Master Zap Message Board - Master Zap YOUTUBE Master Zap BLOG Master Zap mental ray BLOG Welcome to "100% Zap", Håkan "Master Zap" Anderssons website - hosted by LYSATOR ACS Please use "my" URL http://www.Master-Zap.com when linking! |
||||||||||||||||||||||
This document was last updated September 1997
and has been read by one thousand five hundred and twelve people since November 2019
Some advantages of using a plug-in architecture are:
The content creators write or export VRML files, and write scripts in their favourite language. So from a content creators point of view, you only need to know that "this is great", and stop reading here.... :-)
Seriously; none of what is written in this document is of immediate interest to a content creator - this is aimed at browser writers and spec writers! See the Example, in which the content creator only
writes the .WRL and .ATL files.
Don't confuse these VRML Language plugins with NETSCAPE plugins!! The functionality is very similar, but these plugins are plugged directly into the VRML browser, regardless of if the browser itself is running as a
NETSCAPE plugin or not!!
For our purposes we concentrate on the events and the url.
Each .VRP file is loaded (if not already loaded and known, of course!), and the first function in the file is called:
The function is passed one parameter, the languageId as found in the earlier step:
(By including the period or the colon, the module can determine what is being passed).
The function being called simply returns 0 (can't handle this language) or 1 (can handle it). If it passes 1, the search ends.
If no appropriate plugin is found, an error message is displayed, and the user is if possible routed to http://www.vrml.org/plugins to download the appropriate plugin.
The second parameter is a pointer to a struct browserCallback which is a struct containing function pointers for calling back into the VRML browser from the plugin. This struct is described below.
The VPInitInstance function must also set up one "context" for this process to run in (remember, the same language plug-in can be called from many Script nodes in one file. Each of these instances need to keep their data separate.
The handle or pointer to this "local data" is then returned by VPinitInstance. (If there is an error, VPInitInstance returns NULL, and the browser must take appropriate action).
This pointer is kept by the VRML browser, and passed in all subsequent calls from the VRML Browser to the language plug-in.
Numerals are used for node names, field names, function names for browser callbacks, e.t.c. Numerals are not used for actual string processing operations.
A handle can be anything (represented here as a void pointer), simply somthing that uniquely identifies a given event/function name, and that your plugin can understand, and know how to call the correct piece of the script.
You may want to have direct function pointers, you may want to have an index into an array of functions, or you might simply want to remember the string (strdup() it into the handle!).
VPFunctionHandle will be called once for each name the browser needs to resolve in that instance (Script node). The actual calls will be used using that handle, not the string.
If the event doesn't exist in the script, NULL is returned, and the Browser must report an error.
The event handler is called with the instance identifier, the functionHandle as established above (which exact meaning is defined by your VRML plugin), and the data member points to the event data, in the
type declared in the Script node is passed in 'type'.
It's not 100% complete, but shows you how this might work...
Will the content creators need to care?
Notes:
The Moving Worlds Script Node
Script {
exposedField MFString url []
field SFBool directOutputs FALSE
field SFBool mustEvaluate FALSE
# And any number of:
eventIn eventTypeName eventName
field fieldTypeName fieldName initialValue
eventOut eventTypeName eventName
}
The node consists of a set of fields, eventIn's, eventOut's and an url for the script to be executed.Finding the interpreter/run time environment
Finding the scripts "languageId"
When the VRML browser encounters a script node, it looks at the URL, and try to extract information from it:
The PLUGIN directory
Secondly, the directory from which to load the language plug-in's must be established. The following search method is used:
Finding it
The path(s) found in the previous step is searched for files named *.VRP. (Virtual Reality Plugin)
int VPCanHandle( char *languageId ); // Ordinal #0
Running the scripts
Initializing the plugin
When the plugin has been loaded, and it's time to execute the script, the function VPInitInstance is called by the VRML Browser:
void *VPInitInstance(char *URL, struct browserCallback *cbks); // Ordinal #1
Two parameters are given by the VRML Browser to the plugin. The first parameter URL is a pointer to the (full) URL as seen in the url field in the Script node. This way, an inline protocol (such as "javascript:" or similar)
can choose to make a local copy of the string, compile it into an internal bytecode, or whatever. If it's a file-URL of some kind, it's time to go fetch that file now...(or set up another thread to fetch it, or whatever)Browser Callback struct
struct browserCallback {
int (*getStringNumeral)(char *string); // Get a string numeral
int (*sendEvent)(int numeral, void *arguments); // Send an event
void *(*callFunction)(int numeral, void *arguments); // Call a browser function
.
. ?? more ??
.
};
This struct contains the functions the language plugin is allowed to call back into the browser.
Plugin function/event Enumeration
Enumeration can, for efficiencys sake, need to go both ways. The browser calls this fucntion:
void *VPFunctionHandle(void *instance, char *name) // Ordinal #2
The first time the VRML browser is about to call a script, it will need to know the handles of the functions and event-handlers in that script as provided by the plugin.Getting Called
void VPEventIn(void *instance, void *functionHandle, int dataType, void *data) // Ordinal #3
The only way to be called from the Script node is when an event arrives, thus the name of the function.Example: ATLAST language plugin
The Script node
[I.e. what the content creator will need to write!]
DEF OpenVault Script {
# Declarations of what's in this Script node:
eventIn SFTime openVault
eventIn SFBool combinationEntered
eventOut SFTime vaultUnlocked
field SFBool unlocked FALSE
# Implementation of the logic:
url "vault.atl"
}
The VAULT.ATL file
[I.e. what the content creator will need to write!]
variable vaultUnlocked
variable locked
: init ( )
"vaultUnlocked" getStringNumeral vaultUnlocked !
1 locked !
;
: openVault ( SFTime -- )
locked 0= if
vaultUnlocked @ sendEvent \ SFTime is already on the stack
then
;
: combinationEntered ( SFBool -- )
drop \ Drop the SFBool, don't need it
1 locked ! \ Set the variable
;
The ATLAST plugin souce
/* Note; This needs to use a rewritten atlast which keeps all state-variables
in a struct pointed to by atl_instance_ptr, to be able to have several
instances of the interpreter */
#include "atlast.h"
struct browserCallback *browser = NULL;
int sendEvent();
static struct primfcn prims[] = {
{"0SENDEVENT", sendEvent,
{NULL, (codeptr) 0}
};
int VPCanHandle(char *languageId)
{
/* If it's ATLAST, say we can handle it! */
if (strcmp(languageId, "language/atlast") == 0 ||
stricmp(languageId, ".atl") == 0) return TRUE;
return FALSE; /* Dunno what it is! */
}
void *VPInitInstance(char *URL, struct browserCallback *cbks)
{
dictword *dw;
browser = cbks;
atl_init(); /* Initialize ATLAST */
atl_primdef(&prims); /* Let ATLAST know about "sendEvent" */
dw = atl_lookup("init"); /* Find function named "init" */
if (NULL != dw) atl_exec(dw); /* Call it, if present */
return atl_instance_ptr; /* Dirty, just return atlasts instance pointer */
}
void *VPFunctionHandle(void *instance, char *name)
{
atl_instance_ptr = instance; /* Set the right instance */
return atl_lookup(name); /* Lookup the dictionary word */
}
void VPEventIn(void *instance, void *functionHandle, int dataType, void *data)
{
atl_instance_ptr = instance; /* Set the right instance */
/* Put datatypes on the ATLAST stack. Never mind the wierd syntax to push
the items onto the stack, OK?! Blame John Walker... :-) */
switch (dataType)
{
case SFBool:
Push = *(int *)data; /* Pushes bool onto stack */
break;
case SFTime:
Push = 0; /* Make room for a float */
Push = 0; /* Make room for a float */
SREAL0(*(double *)data);
break;
.
.
.
}
atl_exec(functionHandle); /* Calls that ATLAST function */
}
/* Called from the ATLAST code */
void sendEvent()
{
int numeral;
numeral = D0; /* Get the numeral (top of stack) */
Pop; /* Pop item off stack */
/* TBD: Get data of stack. Does datatype need to be passed? */
browser->sendEvent(numeral, data);
}
Note: This example isn't 100% finished (neither is really this whole document), but I trust you all get the general "flavor" by now...!?
/Z