[sldev] Approach to accessing floaters from inside plugins

Soft Noel soft at softnoel.org
Wed Mar 7 22:36:23 PST 2007


I'm working to simplify the Floater interface for the C plugin interface,
proceeding along these lines. Input on the approach would be appreciated
before I start widening the interface with more events and accessors. The
explanation will be in three parts. The first is what a plugin author
would see/use. Here's enough interface to create something like the sample
"Hippo" floater from the wiki:

/*
Functions used:

U32 FloaterOpen( const char *name, const char *filepath );
Open a floater XML description named name located at $pluginpath/filepath,
return handle or 0

void FloaterClose( U32 handle );
Close a floater from the handle returned above, hiding it and revoking any
handlers

BOOL FloaterClickHandlerAdd( U32 floaterhandle, const char *buttonname,
void (ClickFunc*)(U32 floaterhandle, void *userdata) );
Register a callback for button clicks. Callback is:
void Foo( U32 floaterhandle, void *userdata );

BOOL FloaterCommitHandlerAdd( U32 floaterhandle, const char *fieldname,
void (CommitFunc*)(U32 floaterhandle, void *userdata, const char
*fieldtext) );
Register a callback for field commits. Callback is:
void Foo( U32 floaterhandle, void *userdata, const char *fieldtext );
fieldtext is only valid for the duration of the callback.

void FloaterShow( U32 handle );
Show an open floater
*/

#include "linden_plugin_common.h"
#include "lljacksystem.h"
#include "lljackfloater.h"


// Plugin Declaration

JackType interfaces_requested[] =
{
	JACK_TYPE_SYSTEM, // provides Log* functions
	JACK_TYPE_FLOATER, // provides Floater* functions
	JACK_TYPE_END
};

PLUGIN_DECLARE(
	"Hello Floater", "0.01", // Name, version
	"Soft Noel", // Author
	"http://softnoel.org", // Plugin site URL
	PLUGIN_FLAGS_DEFAULT,
	interfaces_requested
);


// Globals

U32 g_floaterhandle;


// Callbacks

void ButtonClickFunc( U32 floaterhandle, void *userdata )
{
	LogPrint( "Clicked button %d", (U32)userdata );
}

void FieldCommitFunc( U32 floaterhandle, void *userdata, const char
*fieldtext )
{
	LogPrint( "Entered <%s> in field %d", fieldtext, (U32)userdata );
}


// Utility

BOOL CreateFloater()
{
	g_floaterhandle = FloaterOpen( "floatername", "filename.xml" );

	if( !g_floaterhandle )
		return FALSE;

	FloaterClickHandlerAdd( g_floaterhandle, "buttonname", ButtonClickFunc,
(void *)1 );
	FloaterCommitHandlerAdd( g_floaterhandle, "fieldname", FieldCommitFunc,
(void *)2 );
	FloaterShow( g_floaterhandle );
}

void DestroyFloater()
{
	// First hides floater and detaches handlers if we don't
	FloaterClose( g_floaterhandle );
}


// Body

BOOL PluginStartup()
{
	BOOL success;

	LogPrint( "Starting up Hello Floater" );
	success = CreateFloater();

	if( !success )
		LogPrint( "Failed to create floater" );

	return success;
}

void PluginShutdown( BOOL crashing )
{
	DestroyFloater();
}




The second part to describe is the API delivery mechanism. All of the
Floater* functions above are delivered in structs of function pointers,
and the plugin-side library tucks those pointers into global function
pointers, making the Log* and Floater* function pointers callable as
though they were local functions. The interface is created as described in
the LLJack source drop, except that the interface is now all C structs
with manually filled function pointers, rather than the class with virtual
functions that raised concern last week.



The third part is what happens inside the SL client. The biggest obstacle
to overcome was the traditional special class that's created for every
floater; we need everything to be data driven from the floater class on
down to the callback pointers. We also need to isolate the plugin from
potential interface changes and we want to clean up abandoned resources
and sanitize parameters when we can reasonably provide a meaningful log
message instead of a viewer crash.

Here are the present actors:

These are C++ classes (inside the viewer only -- C++ is never exported to
the plugin):

LLFloaterPluginGlue
LLFloaterPluginGlueManager
LLFloaterPluginGlue is a special class derived from LLFloater. Unlike most
viewer floaters, this one contains no static data. It currently carries
the the ID of the plugin that owns the class. The manager class assigns a
handle to the floater that the plugin uses in place of a pointer so we can
catch attempts at using abandoned or uninitialized resources.


LLFloaterCallback
LLFloaterCallbackCommit : LLFloaterCallback
LLFloaterCallbackClick : LLFloaterCallback
LLFloaterCallbackManager
LLFloaterCallback is the base class for the two specialized callback types
above. The LLFloaterCallback objects each hold the original function
pointer and userdata registered by a plugin, as well as the floater handle
and plugin ID. This lets us use a single dispatch function (below) for the
handler for each event type and put the handle of the LLFloaterCallback in
as the callback userdata. Having this data available to the manager class
also lets us find all callbacks associated with a plugin ID or window
handle for selective destruction.

These are all non-class "C"-able functions:

CommitHandler
ClickHandler
These are generic internal functions registered in place of all plugin
floater event handlers for commits and clicks. A call to one of these asks
the LLFloaterPluginCallbackManager for the LLFloaterCallback object
associated with the handle (userdata) so the owning plugin can be
identified and validated, and its requested userdata and callback pointer
retrieved.


FloaterOpen
Requests the LLFloaterPluginGlueManager create a LLFloaterPluginGlue class
(derived from LLFloater), assign it a handle, and load the XML from the
plugin directory. Returns the handle.

FloaterClose
Hides a floater, tells the LLFloaterCallbackManager to deregister all
callbacks associated with this floater handle, and (not yet) unloads the
floater. All handles are invalidated.

FloaterClickHandlerAdd
FloaterCommitHandlerAdd
Ask the LLFloaterPluginGlueManager for a floater by handle, ask the
LLFloaterCallbackManager for a handle to identify this callback, store the
plugin's callback and userdata so we can instead use
CommitHandler/ClickHandler for the callback and the callback handle for
the userdata.

FloaterShow
Ask the LLFloaterPluginGlueManager for a floater by handle and show it if
it exists




This is a thin vertical slice of the UI. More widget types will be added,
as will manual callback removal, window hiding, etc. What I want is input
on whether this is the type of interface we want to give plugin authors.



Looking forward on the UI front, I could also use ideas on how to handle
menu additions that aren't known at build time. For a first pass, I'm
probably just going to allow plugins to tack menu entries onto the bottom
of existing menus only. Ideally, entries should be able to be placed more
logically. Any ideas on the best way of doing this? Dale Glass suggested
it would be good if groups of items could be named in the existing menu
files, or perhaps the separator bars could be named so items could be
explicitly added just before or after them. Anyone seen other approaches
for dynamically extending menus? Or do we want to be more flexible than
forcing everything to be a submenu of a "Plugin" menu with each plugin as
a single item?




More information about the SLDev mailing list