FreeWRL EAI Interface Design and Function.
December 2, 2008
John Stewart, CRC Canada
With notes from Dave Joubert.
Definitions:
EAI = External Authoring Interface; to control the X3D
Scene Graph from an external (Java) program. FreeWRL supports the EAI, with the
exception of the two update methods. http://freewrl.sourceforge.net/EAI.html
SAI = Scene Authoring Interface; a set of Java methods to
extend the functionality of SAI. FreeWRL supports external SAI via java
programs. See Web3d.org website.
MIDI – A digital data bus used mainly for real-time
rendering of music. See www.midi.org.
Introduction:
The EAI was originally a Java-based method of controlling a VRML Scene-graph. FreeWRL has had Java/EAI functionality for approximately one decade. The EAI code within FreeWRL has been expanded to handle external SAI commands.
However, the method of implementation lends itself to allowing control of the X3D/VRML (hereafter just referred as X3D) Scene-graph from other languages and applications (possibly from other nodes in a network) so it is not just Java based. For instance, the MIDI implementation from CRC uses this methodology.
This document is targeted at those who are interested in experimenting with control of X3D, or having X3D control external applications. Some esoteric commands do not yet have supporting documentation, but what is here should enable one to fully control an X3D Scene Graph.
Method of communication:
Client/server based.
Usual method:
Controlling command from program
ˆ
FreeWRL;
FreeWRL sends acknowledgment ˆ
controlling program.
We will describe this command/ack protocol here.
When FreeWRL is started, eg from the following script:
freewrl
root.wrl --eai &
java EAIAddRemove
the following happens:
- FreeWRL starts, reads its command-line parameters, and goes into ÒbackgroundÓ mode via the shell command Ò&Ó;
- FreeWRL sees the Ò—eaiÓ parameter, opens a socket via the internal ÒconEAIorCLASSÓ function;
- FreeWRL then listens to the socket for commands from an external application. The following is the actual code for this open:
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr
= htonl(INADDR_ANY);
servaddr.sin_port =
htons(EAIport+socketincrement);
while (bind((*EAIsockfd), (struct sockaddr *)
&servaddr, sizeof(servaddr)) < 0) {
loopFlags &= ~NO_EAI_CLASS;
return FALSE;
}
- Where EAIport=9887, and socketincrement=0.
- In the Java EAI implementation, the code to open the Java side (in FreeWRLEAI.java) is:
while (EAISocket == null) {
try {
sock = new Socket("localhost",9877);
} catch (IOException e) {
É
try {
EAIin = new BufferedReader( new InputStreamReader(sock.getInputStream()));
} catch (IOException e) {
System.out.print ("error reiniting data input stream");
}
- FreeWRL will continue to operate while socket communication is established.
- Command/responses are outlined below.
Notes
from Dave Joubert:
(Dave wrote a TCL interface
to FreeWRL, using this EAI socket interface)
The EAI is inherently Sync and
Async and this is critically important if you decide to write this as a single
threaded model without Listeners.
Any incoming packets could be
bigger than your socket buffer. You will have to accumulate your socket reads
into a complete packet first before taking any action.
Do not give in to the temptation of writing it as an async
model. You will soon hit the problem where the main code thread sets up an
object, and then tries to use it. If you had not yet received the expected
reply from the initial command, the second command will fail.
So, you need to block immediately after sending a packet
and wait for a reply if the command requires it. Each command that is about to
call the IO routine, needs to probably pass at least three pieces of
information: what to send, whether a reply is expected, and what to do with the
reply. (You could handle the reply in the caller, but because the IO routine
will have to cope with events, and because a lot of the reply-handling code
will be similar, it is best to do it in two stages)
The blocking has to be inside a loop, that can re-block if
it needs to, because:
A) You might not yet have the whole reply
B) You may have got an event packet while waiting for the
reply
(B) is very important if you are building a large
interactive world. It costs a lot of time to build the whole thing in two
separate loops, ie build all the objects with the required sensors and loop
through again and make all the sensors active. Conversely, if you build it in
one loop, your user is bound to wave his/her mouse around while it is building,
thus triggering an event.
The best approach I found when you get an event packet
while waiting for a reply packet, is to queue the event packets until all the
replies have been received. This is critically important when you do something
complicated in response to an event, like change the colour of an object,
because then you will be waiting for a reply on that request as well as waiting
for the first reply.
You also need to keep the event buffer safe, ie do not
empty it out prior to blocking for a reply, and do not leave the main IO loop until
the event buffer is empty.
Commands sent from the
application to FreeWRL
Overview: (Specifics are
found further down this document)
All commands/data are sent/received as ASCII data. While this may seem inefficient, it is not only easier to debug, it is easier to create applications in different operating systems. Besides, most commands are short, smaller than the minimum packet size, so network efficiency is not a reason for binary data.
Note however, that the FreeWRL side expects commands in a STRICT format, otherwise errors will occur. Make sure that the syntax of data is correct, because error recovery is sparse, at best.
FreeWRL will accept the following list of commands:
#define GETNODE 'A'
#define SENDCHILD 'C'
#define SENDEVENT 'D'
#define GETVALUE 'E'
#define GETFIELDTYPE 'F'
#define REGLISTENER 'G'
#define ADDROUTE 'H'
#define REREADWRL 'I'
#define DELETEROUTE 'J'
#define GETNAME 'K'
#define GETVERSION 'L'
#define GETCURSPEED 'M'
#define GETFRAMERATE 'N'
#define GETURL 'O'
#define REPLACEWORLD 'P'
#define LOADURL 'Q'
#define VIEWPOINT 'R'
#define CREATEVS 'S'
#define CREATEVU 'T'
#define STOPFREEWRL 'U'
#define UNREGLISTENER 'W'
#define GETRENDPROP 'X'
#define GETENCODING 'Y'
#define CREATENODE 'a'
#define CREATEPROTO 'b'
#define UPDNAMEDNODE 'c'
#define REMNAMEDNODE 'd'
#define GETPROTODECL 'e'
#define UPDPROTODECL 'f'
#define REMPROTODECL 'g'
#define GETFIELDDEFS 'h'
#define GETNODEDEFNAME 'i'
#define GETROUTES 'j'
#define GETNODETYPE 'k'
#define MIDIINFO 'l'
#define MIDICONTROL 'm'
and the following DATA types:
#define EAI_SFBool
'b'
#define EAI_SFColor
'c'
#define EAI_SFFloat
'd'
#define EAI_SFTime
'e'
#define EAI_SFInt32
'f'
#define EAI_SFString
'g'
#define EAI_SFNode
'h'
#define EAI_SFRotation 'i'
#define EAI_SFVec2f 'j'
#define EAI_SFImage
'k'
#define EAI_MFColor
'l'
#define EAI_MFFloat
'm'
#define EAI_MFTime
'n'
#define EAI_MFInt32
'o'
#define EAI_MFString
'p'
#define EAI_MFNode 'q'
#define EAI_MFRotation 'r'
#define EAI_MFVec2f
's'
#define EAI_MFVec3f
't'
#define EAI_SFVec3f
'u'
#define EAI_MFColorRGBA 'v'
#define EAI_SFColorRGBA 'w'
#define EAI_MFBool 'x'
#define EAI_FreeWRLPTR 'y'
#define EAI_MFVec3d
'A'
#define EAI_SFVec2d
'B'
#define EAI_SFVec3d
'C'
#define EAI_MFVec2d
'D'
#define EAI_SFVec4d
'E'
#define EAI_MFDouble 'F'
#define EAI_SFDouble
'G'
#define EAI_SFMatrix3f 'H'
#define EAI_MFMatrix3f 'I'
#define EAI_SFMatrix3d 'J'
#define EAI_MFMatrix3d 'K'
#define EAI_SFMatrix4f 'L'
#define EAI_MFMatrix4f 'M'
#define EAI_SFMatrix4d 'N'
#define EAI_MFMatrix4d 'O'
#define EAI_SFVec4f
'P'
#define EAI_MFVec4f
'Q'
#define EAI_MFVec4d
'R'
An example of the data flow using the FreeWRL test EAIAddRemove.java:
// open communication
Browser browser = Browser.getBrowser(this);
if ((browser) == null) {
System.out.println("FATAL ERROR! no browser");
}
// get a node from the FreeWRL X3D scene graph
Node root = browser.getNode("ROOTNODE");
// Instantiate (get handle to) the EventIn objects
addChildren = (EventInMFNode)
root.getEventIn("addChildren");
removeChildren = (EventInMFNode)
root.getEventIn("removeChildren");
// Instantiate a lovely blue ball
shape1 = browser.createVrmlFromString(
"Transform{translation -2.3 2.1 0 children Shape{\n"+
" appearance Appearance {\n" +
" material Material {\n"
+
" diffuseColor
0.2 0.2 0.8\n" +
" }\n" +
" }\n" +
" geometry Sphere {}\n" +
"}}\n");
Getting FreeWRL X3D node ÒROOTNODEÓ
Application sends:
1A ROOTNODE
Which is:
- command sequence number 1;
- command ÒAÓ – GETNODE; (see table above)
- node name in the X3D Scene Graph: ÒROOTNODEÓ.
FreeWRL replies with:
RE
1227295777.240668
1
0 8724736
RE_EOT
Which is:
- a response (ÒREÓ), as opposed to an asynchronous event (ÒEVÓ);
- system time;
- Ò1Ó = sequence number; should match the command sequence number;
- 2 node ÒhandlesÓ, currently a pair of (0, memoryPtr), but the actual meaning might change – just treat these as constants.
- ÒRE_EOTÓ – response END OF TEXT.
Getting FreeWRL X3D node EVENTIN
2F 0 8724736 addChildren eventIn
Which is:
- command sequence number 2;
- command ÒFÓ – GETFIELDTYPE;
- node handle pair;
- field of node;
- eventType.
FreeWRL returns:
RE
1227295777.255864
2
8724736 120 -10 q 0 eventIn
RE_EOT
Which is:
- a response (ÒREÓ), as opposed to an asynchronous event (ÒEVÓ);
- system time;
- Ò2Ó = sequence number; should match command sequence number;
- a line containing:
o Ò872..Ó part of the node handle,
o Ò120Ó offset of data in the node (treat as constant)
o Ò-10Ó data length for routing; negative numbers mean that they are varying size, as opposed to a specific number of bytes;
o ÒqÓ = EAI_MFNode – the type of the addChildren field;
o Ò0Ó FreeWRLs internal type;
o ÒeventInÓ – telling the application what type FreeWRL thinks this is.
- and, the ÒEOTÓ marker.
FreeWRL sending in x3dv Code:
4S Transform{translation -2.3 2.1 0 children Shape{
appearance
Appearance {
material Material {
diffuseColor 0.2 0.2 0.8
}
}
geometry
Sphere {}
}}
EOT
Which is:
- command sequence number 4;
- command ÒSÓ – CREATEVS (create VRML from string)
- input text
- the string ÒEOTÓ at the START of the line.
Response:
RE
1227295777.271929
4
0 9152048
RE_EOT
Which can be decoded EXACTLY as for the ÒROOTNODEÓ command, above.
Adding, removing this child:
É
addChildren.setValue(shape1);
É
removeChildren.setValue(shape1);
Sent:
10C 8724736 120 addChildren 9152048
and
16C 8724736 120 removeChildren 9152048
both return responses, eg:
RE
1227295777.326321
10
0
RE_EOT
Command Specifics.
In this section, we will discuss individual commands and responses.
Please Note:
- As FreeWRL was initially Perl based, then C based, sometimes extra data is provided in commands/responses. It is easier to leave obsolete data in at times than to remove it, risking version problems. You will see this in the node handle pairs; certain times you will have a response pair (eg, Ò0 44535Ó) other times you will just see the second part of this pair – (Ò44535Ó) this is a relic of the Perl code.
-
If the source code and this document do not agree, then
the source code is correct, and you have been granted the right to complain
and/or fix the documentation! Please!
GETNODE 'A'
Description: Goes
through the DEF nodes, and finds one matching the string.
Expects: A valid DEF name for the current SceneGraph. eg:
1A ROOTNODE
Returns: A Ònode pairÓ in a standard response format –
eg:
0 8724736
The first number
is obsolete and can be ignored; the second number
is the actual node ÒhandleÓ.
SENDCHILD 'C'
Description: send
a node handle to the node/offset of a parent.
Expects:
10C 8724736 120 addChildren 9152048
which is:
-
sequence number + command;
-
node handle;
-
offset of field within node (see GETFIELDTYPE)
-
string ÒaddChildrenÓ or ÒremoveChildrenÓ
-
node to add/remove
Returns: standard
return; eg:
RE
1227295777.326321
10
0
RE_EOT
SENDEVENT 'D'
Description: Sends
an event to a field of a node.
Expects: eg, for a SFVec3f:
12Du 9309664
212 0 1.0 2.0 3.0
Returns: THERE IS NO RETURN
VALUE – this lets many events
get sent
quickly.
GETVALUE 'E'
Description: Get
the value of a field within a node.
Expects: Node handle, field offset, type, and data size. Eg:
15E 9199216 168 i 16
Where
the ÒIÓ – EAI_SFRotation, and 16 = data size in bytes.
Returns: field value.
RE
1227714490.500232
15
0.000000 0.000000 1.000000
9.531744
RE_EOT
Different
field types have different return values.
SFInt32,
SFVec*, SFColor*, SFFloat, SFTime int/float/double values follow the above
structure.
MFInt32,
MFVec*, MFColor*, MFFloat, MFTime int/float/double values follow the following
structure: (example is a MFVec3f, 3 values)
RE
1227730825.741650
12
3
0.000000 0.000000 0.000000
1.000000 1.000000 1.000000
2.000000 2.000000 2.000000
RE_EOT
Other
types are shown here:
SFBool: either
TRUE or FALSE
SFString: eg:
ÒStarting PositionÓ
MFString: eg: Òstring 1Ó Òstring 2Ó
GETFIELDTYPE 'F'
Description: Get
the field type of the field of a node.
Expects:
2F 0 8724736 addChildren eventIn
Which is:
- command sequence number 2;
- command ÒFÓ – GETFIELDTYPE;
- node handle pair;
- field of node;
- eventType.
Returns:
RE
1227295777.255864
2
8724736 120 -10 q 0 eventIn
RE_EOT
Which is:
- a response designator (ÒREÓ);
- system time;
- Ò2Ó = sequence number; should match command sequence number;
- a line containing:
o Ò872..Ó part of the node handle,
o Ò120Ó offset of data in the node (treat as constant)
o Ò-10Ó data length for routing; negative numbers mean that they are varying size, as opposed to a specific number of bytes;
o ÒqÓ = EAI_MFNode – the type of the addChildren field;
o Ò0Ó FreeWRLs internal type;
o ÒeventInÓ – telling the application what type FreeWRL thinks this is.
- and, the ÒEOTÓ marker.
REGLISTENER 'G'
Description: Places
a route in the routing table so that changes get sent.
Expects: eg: for a ProximitySensor position_changed event:
15G 9148224 180 u 12
ASYNC Callback Event: Every
time this route runs, the following will be sent:
EV
1227733533.828239
15
0.000000 0.000000 6.900000
EV_EOT
Note that this is an ÒEVÓ not a ÒREÓ and that the data can come any time between REs.
Note also that the EV returns the ORIGINAL sequence number of the REGLISTENER command; this sequence number is your
handle to help you decode the meaning of each and every ASYNC Callback. (the handle number is "15", in the above example)
ADDROUTE 'H'
Description: Adds
a route to the routing table
Expects: 4
values, from node handle/field, to node handle/field. Eg:
10H 988342 fraction_changed 9824211 set_fraction
Returns: In case of error, an error message will be printed
on the console,
and a return value of Ò1Ó will be sent. (Ò0Ó = ok)
RE
1227633671.152842
10
0
RE_EOT
REREADWRL 'I'
Description: not
documented yet
Expects:
Returns:
DELETEROUTE 'J'
Description: Deletes
a route to the routing table
Expects: 4
values, from node handle/field, to node handle/field. Eg:
10J 988342 fraction_changed 9824211 set_fraction
Returns: In case of error, an error message will be printed
on the console,
and a return value of Ò1Ó will be sent. (Ò0Ó = ok)
RE
1227633671.152842
10
0
RE_EOT
GETNAME 'K'
Description: Get
the name of the Browser (in this case, FreeWRL)
Expects:
7K
Returns: one line containing the name of the browser. Eg:
RE
1227563622.598430
7
FreeWRL VRML/X3D Browser
RE_EOT
GETVERSION 'L'
Description: Get
the current (installed) version of FreeWRL
Expects:
6L
Returns: one line containing the installed version of
FreeWRL. Eg:
RE
1227563622.598430
6
V1.20.4
RE_EOT
GETCURSPEED 'M'
Description: Get
the current navigation speed.
Expects:
7M
Returns: one line containing the current Nav speed. (have to
fill in max
Value) minimum value is 0.0 – user is
stationary. Eg:
RE
1227563622.598430
7
0.0
RE_EOT
Returns:
GETFRAMERATE 'N'
Description: Gets
the current frame rate in frames per second.
Expects:
8K
Returns:
RE
1227563622.598430
8
106.014858
RE_EOT
GETURL 'O'
Description: Gets
the url to the currently loaded world.
Expects:
6O
Returns:
RE
1227628201.224926
6
/Users/john/Desktop/freewrl/tests/AddRemove/root.wrl
RE_EOT
REPLACEWORLD 'P'
Description: not
documented yet
Expects:
Returns:
LOADURL 'Q'
Description: not
documented yet
Expects:
Returns:
VIEWPOINT 'R'
Description: not
documented yet
Expects:
Returns:
CREATEVS 'S'
Description: Parse
(but do not add to scene graph) code.
Expects: Valid
x3dv classic code, followed by the string ÒEOTÓ on its own
line. Eg:
4S Transform{translation -2.3 2.1
0 children Shape{
appearance Appearance {
material Material {
diffuseColor 0.2 0.2 0.8
}
}
geometry Sphere {}
}}
EOT
Returns: node handle pairs; first number of pair is ignored,
but should
be zero. Eg:
RE
1227295777.271929
4
0 9152048
RE_EOT
CREATEVU ÔTÕ
Description: Parse
a URL, and return a list of node
handle pairs.
Expects: 16T
/FreeWRL/freewrl/freewrl/tests/1.wrl
Returns: node handle pairs; first number of pair is ignored,
but should
be zero. Eg:
RE
1227295777.271929
4
0 9152048
RE_EOT
STOPFREEWRL 'U'
Description: not
documented yet
Expects:
Returns:
UNREGLISTENER 'W'
Description: removes
a registered listener.
Expects: 16W 9208928 180
u 12
Returns:
RE
1227733885.542056
16
0
RE_EOT
GETRENDPROP 'X'
Description: not
documented yet
Expects:
Returns:
GETENCODING 'Y'
Description: not
documented yet
Expects:
Returns:
CREATENODE 'a'
Description: not
documented yet
Expects:
Returns:
CREATEPROTO 'b'
Description: not
documented yet
Expects:
Returns:
UPDNAMEDNODE 'c'
Description: not
documented yet
Expects:
Returns:
REMNAMEDNODE 'd'
Description: not
documented yet
Expects:
Returns:
GETPROTODECL 'e'
Description: not
documented yet
Expects:
Returns:
UPDPROTODECL 'f'
Description: not
documented yet
Expects:
Returns:
REMPROTODECL 'g'
Description: not
documented yet
Expects:
Returns:
GETFIELDDEFS 'h'
Description: not
documented yet
Expects:
Returns:
GETNODEDEFNAME 'i'
Description: not
documented yet
Expects:
Returns:
GETROUTES 'j'
Description: not
documented yet
Expects:
Returns:
GETNODETYPE 'k'
Description: not
documented yet
Expects:
Returns:
MIDIINFO 'l'
Description: not
documented yet
Expects:
Returns:
MIDICONTROL 'm'
Description: not
documented yet
Expects:
Returns: