Wiki

Clone wiki

asss / Interface

Interfaces

These are without a doubt the most important tools one will use in asss. They allow modules to pass functionality to other modules. Simple modules will primarily use other module's interfaces and while registering none of their own. Most modules don't implement more than one interface. Related concepts are Advisers and Callbacks.

Properties of interfaces

  • They are a collection of functions to be implemented, with a specific identifier and name.
  • Other modules call functions in the interface for the purpose of getting their tasks done.
  • Generally implemented only once in a particular scope (global or arena)
  • If a second interface is registered in a scope, it replaces the existing implementation (unlike Callbacks).
  • Though atypical, interfaces can be requested by name so that modules can pick the implementation they want.
  • All functions must be implemented.
  • The interface identifier resolves to a version-string which should change when the interface type changes, to ensure other modules don't try to use a different interface with the same identifier.
  • Registered interface implementations have reference counts which represent how many times the interface has been requested and not released. Interfaces cannot be unregistered until the reference count is zero.

Conventions

  • struct name: Iinterfacetype
  • interface identifier: I_INTERFACETYPE
  • interface identifier version: "interfacetype-versionnumber"

Interface types must start with INTERFACE_HEAD_DECL to reserve enough space for bookkeeping.

Interface instantiations must start with INTERFACE_HEAD_INIT(I_INTERFACETYPE, "name") to store the identifier and name. The name is of little consequence to most interfaces, but if multiple implementations exist for one interface, the name should be unique for each implementation. By convention, the name of the interface is the name of the module.

Interfaces are registered using Imodman's RegInterface(&implementation, <scope>) where <scope> is an arena or the identifier ALLARENAS. Use UnregInterface to try to unregister your interface. UnregInterface will return the reference count, a non-zero value means that the function failed because your interface is still in use. You shouldn't wait for the reference count to be 0, but instead immediately fail from the function that was calling UnregInterface.

Examples

Using an interface

Hello, world example. Most modules use interfaces like this, keeping a global interface pointer for the entire module to use while it's loaded.

/* global interface ptr */
local Ichat *chat;

/* entry point for the 'helloworld' module */
EXPORT int MM_helloworld(int action, Imodman *mm, Arena *a)
{
	if (action == MM_LOAD)
	{
		/* get the chat interface */
		chat = mm->GetInterface(I_CHAT, ALLARENAS);
		
		/* we couldn't get the interface, unable to load successfully */
		if (!chat)
			return MM_FAIL;
		
		/* use the SendArenaMessage function in the Ichat interface to send a message to the whole zone */
		chat->SendArenaMessage(ALLARENAS, "Hello, world!");
		
		/* return success */
		return MM_OK;
	}
	else if (action == MM_UNLOAD)
	{
		chat->SendArenaMessage(ALLARENAS, "Goodbye, world!");
		
		/* we're done with chat, free it up so later chat can be unregistered */
		mm->ReleaseInterface(chat);
		
		return MM_OK;
	}
	return MM_FAIL;
}

A slightly different way to use an interface:

/* a command function */
local void Ckick(const char *tc, const char *params, Player *user, const Target *target)
{
	if (target->type == T_PLAYER)
	{
		/* the ?kick command was invoked by PlayerA (user) targeting PlayerZ (target->u.p) */
		
		/* get the interface */
		Icapman *capman = mm->GetInterface(I_CAPMAN, ALLARENAS);
		if (capman)
		{
			/* we can only proceed if there is an implementation for I_CAPMAN.
			 * use the HigherThan function to see if one player has command authority
			 * over another. */
			if (capman->HigherThan(user, target->u.p))
			{
				/* now we know that PlayerA can kick PlayerZ, and carry it out here. */
			}
		}
		/* release the interface because we're done with it. */
		mm->ReleaseInterface(capman);
	}
}

Interface type

/* defines the interface identifier */
#define I_CAPMAN "capman-3"

/* defines the interface for capability checking */
typedef struct Icapman
{
	INTERFACE_HEAD_DECL

	int (*HasCapability)(Player *p, const char *cap);
	int (*HasCapabilityByName)(const char *name, const char *cap);
	int (*HasCapabilityInArena)(Player *p, Arena *a, const char *cap);
	int (*HigherThan)(Player *a, Player *b);
} Icapman;

Interface implementation

/* prototype the functions we are implementing */
local int HasCapability(Player *p, const char *cap);
local int HasCapabilityByName(const char *name, const char *cap);
local int HasCapabilityInArena(Player *p, Arena *a, const char *cap);
local int HigherThan(Player *a, Player *b);

/* define the interface implementation */
local Icapman capint =
{
	INTERFACE_HEAD_INIT(I_CAPMAN, "capman-groups")
	HasCapability, HasCapabilityByName, HasCapabilityInArena, HigherThan
};

Setting up and cleaning up implementations

/* The module manager function, the entry point to the module */
EXPORT int MM_capman(int action, Imodman *mm, Arena *arena)
{
	if (action == MM_LOAD)
	{
		mm->RegInterface(&capint, ALLARENAS);
		return MM_OK;
	}
	else if (action == MM_UNLOAD)
	{
		/* if this function returns non-zero, then people are still using our interface and we can't unload */
		if (mm->UnregInterface(&capint, ALLARENAS))
			return MM_FAIL;

		return MM_OK;
	}
	return MM_FAIL;
}

Updated