Authors often require that VRML worlds change
dynamically in response to user inputs, external events, and the current
state of the world. The proposition "if the vault is currently
closed AND the correct combination is entered, open the vault"
illustrates the type of problem which may need addressing. These kinds
of decisions are expressed as Script nodes (see "3.40 Script") that receive events from other nodes,
process them, and send events to other nodes. A Script node can also
keep track of information between subsequent executions (i.e., retaining
internal state over time).
This section describes the general mechanisms
and semantics of all scripting language access protocols. Note that
no particular scripting language is required by the VRML standard. Details
for two scripting languages are in Appendix B, "Java Scripting Reference"
and Appendix C, "JavaScript Scripting Reference",
respectively. If either of these scripting languages are implemented,
the Script node implementation shall conform with the definition described
in the corresponding appendix.
Event processing is performed by a program or
script contained in (or referenced by) the Script node's url
field. This program or script may be written in any programming language
that the browser supports.
design note
The lack of a required scripting language for
VRML is a problem for content creators who want their content to run
on all VRML browsers. Unfortunately, the VRML community was unable
to reach consensus on a language to require. The leading candidates
were Java, JavaScript (or possibly a subset of JavaScript), and both
Java and JavaScript. The scripting language situation isn't completely
chaotic, however. Appendix C, Java Scripting Reference, and Appendix
D, JavaScript Scripting Reference, define the language integration
specifications if a browser chooses to implement one of these two.
A Script node is activated when it receives
an event. The browser shall then execute the program in the Script node's
url field (passing the program to an external interpreter if
necessary). The program can perform a wide variety of actions including
sending out events (and thereby changing the scene), performing calculations,
and communicating with servers elsewhere on the Internet. A detailed
description of the ordering of event processing is contained in "2.10 Event processing."
Script nodes may also be executed after they
are created (see "2.12.3 Initialize() and shutdown()").
Some scripting languages may allow the creation of separate processes
from scripts, resulting in continuous execution (see "2.12.6 Asynchronous scripts").
Script nodes receive events in timestamp order.
Any events generated as a result of processing an event are given timestamps
corresponding to the event that generated them. Conceptually, it takes
no time for a Script node to receive and process an event, even though
in practice it does take some amount of time to execute a Script.
design note
Scripts are also activated when the file is
loaded (see Section 2.12.3, Initialize and Shutdown). Some scripting
languages allow the creation of asynchronous threads of execution,
allowing scripts to be continuously active (see Section 2.7.6, Asynchronous
Scripts). But it is expected that most scripts will act as "glue"
logic along routes and will be executed only when they receive events.
design note
Creating Script nodes that take a long time
to process events (e.g., half a second) is a bad idea, since one slow
Script node might slow down the entire VRML browser. At the very least,
slow scripts will cause browsers problems as they try to deal with
events with out-of-date time stamps, since even if the script takes
three seconds to process an event, the events it generates will have
time stamps equal to the original event.
If you want a Script node to perform some lengthy
calculation, it is best to use a language like Java that allows the
creation of separate threads, and perform the lengthy calculation
in a separate thread. The user will then be able to continue interacting
with the world while the calculation is proceeding.
2.12.3 Initialize() and shutdown()
The scripting language binding may define an
initialize() method. This method shall be invoked before
the browser presents the world to the user and before
any events are processed by any nodes in the same VRML file as the Script
node containing this script. Events generated by the initialize()
method shall have timestamps less than any other events generated by
the Script node. This allows script initialization tasks to be performed
prior to the user interacting with the world.
design note
Note that the specification is fuzzy about
exactly when initialize() is called. The only requirement is that
it be called before the Script generates any events (which can happen
only after either an event has been received or initialize() is called).
However, implementations should call the initialize() method as soon
as possible after the Script node is created. For example, you might
write a script that has an initialize() method that starts a thread
that establishes and listens to a connection to a server somewhere
on the network. Such a Script might not generate any events until
it receives a message from the server, so an implementation that never
called its initialize() method in the first place would, technically,
be compliant with the requirements of the VRML specification.
Requiring that the initialize() method be called
"as soon as possible" may not be desirable, either. Implementations
may have prefetching strategies that call for loading part of the
world into memory but not initializing it until the user performs
some action (e.g., walks through the teleportation device). In this
case, browser implementors are trusted to make reasonable decisions.
tip
It is sometimes useful to create Scripts that
have only an initialize() method. This technique can be used to ensure
that exposedFields along an event cascade route start out with reasonable
values. The Script simply generates an initial event (with a value
that might be specified as a field of the Script, for example) in
its initialize() method. The same technique is also useful for generating
geometry or textures at load time; transmitting code that generates
nodes rather than specifying the nodes explicitly, can save lots of
bandwidth.
design note
If a Script has no eventIns and doesn't start
up an asynchronous thread, then it can safely be deleted as soon as
its initialize() method has been called. There is no way for such
a Script to ever generate events after the initialize() method is
finished.
Likewise, the scripting language binding may
define a shutdown() method. This method shall be invoked when
the corresponding Script node is deleted or the world containing the
Script node is unloaded or replaced by another world. This method may
be used as a clean-up operation, such as informing external mechanisms
to remove temporary files. No other methods of the script may be invoked
after the shutdown() method has completed, though the shutdown()
method may invoke methods or send events while shutting down. Events
generated by the shutdown() method that are routed to nodes that
are being deleted by the same action that caused the shutdown()
method to execute will not be delivered. The deletion of the Script
node containing the shutdown() method is not complete until the
execution of its shutdown() method is complete.
design note
Again, the specification doesn't precisely
specify when shutdown() is called. Unless you are writing a script
that starts separate threads, you probably won't need a shutdown()
method.
2.12.4 EventsProcessed()
The scripting language binding may define an
eventsProcessed() method that is called after one or more events
are received. This method allows Scripts that do not rely on the order
of events received to generate fewer events than an equivalent Script
that generates events whenever events are received. If it is used in
some other time-dependent way, eventsProcessed() may be nondeterministic,
since different browser implementations may call eventsProcessed()
at different times.
For a single event cascade, a given Script node's
eventsProcessed() method shall be called at most once. Events
generated from an eventsProcessed() method are given the timestamp
of the last event processed.
design note
Sophisticated implementations may determine
that they can defer executing certain scripts, resulting in several
events being sent to a script at once. The eventsProcessed()
routine is an optimization that lets the script creator be more efficient
in these cases. For example, if you create a simple Script that receives
set_a and set_b events and generates sum_changed events where sum
= a + b, it is more efficient to calculate the sum and generate the
sum_changed event in an eventsProcessed() routine, after all
set_a and set_b events have been received. The end result is the same
as generating sum_changed events whenever a set_a or set_b event is
received, but fewer events will be generated.
Of course, if it is important that events for
all of the changes to sum are generated, eventsProcessed()
should not be used. For example, you might create a script that recorded
the time and value of each event it receives, which could be used
to generate a history of the sum over time. Most of the time, however,
only the most current result is of interest.
Scripts that have access to other nodes (via
SFNode/MFNode fields or eventIns) and that have their directOutput
field set to TRUE may directly post eventIns to those nodes. They may
also read the last value sent from any of the node's eventOuts.
When setting a value in another node, implementations
are free to either immediately set the value or to defer setting the
value until the Script is finished. When getting a value from another
node, the value returned shall be up-to-date; that is, it shall be the
value immediately before the time of the current timestamp (the current
timestamp returned is the timestamp of the event that caused the Script
node to execute).
Script nodes that are not connected by ROUTE
statements may be executed asynchronously. If multiple directOutput
Scripts read from and/or write to the same node, the results may be
undefined.
design note
The directOutput field is a hint to the browser
that the Script may directly read or write other nodes in the scene,
instead of just receiving and sending events through its own eventIns
and eventOuts. When directOutput is FALSE (the default), several optimizations
are possible that cannot be safely performed if the Script might directly
modify other nodes in the scene. If we assumed that browsers could
examine the Script's code and look at the calls it makes before execution,
then this hint wouldn't be necessary. However, it is assumed that
scripts are black boxes and browsers may not be able to examine their
code. For example, Java byte code may be passed directly to a Java
interpreter embedded in the computer's operating system, separate
from the VRML browser.
If a Script node, with its directOutput set
to FALSE, directly modifies other nodes, results are undefined. Browsers
are not required to check for this case because it would slow down
scripts that have set the field correctly (slowing down execution
of the common case to test for a rare error condition would violate
the design principle that VRML should be high performance). Errors
due to incorrectly setting the directOutput flag are likely to be
hard to find, since they will cause some browsers to make invalid
assumptions about what optimizations they can perform and have no
effect on other browsers that perform different optimizations.
Scripts that have their directOutput field
set to TRUE can only read or write nodes to which they have access.
There are four ways for scripts to get access to other nodes:
- Scripts with SFNode or MFNode fields have
access to the nodes in those fields.
- Scripts with SFNode or MFNode eventIns have
access to the nodes in those events.
- Scripts that create nodes using the createVrmlFromString()
routine (see Section 2.7.10, Browser Script Interface) have access
to the nodes they create.
- If a script has access to a node, and that
node has SFNode or MFNode eventOuts, then the script has access to
all of the nodes in those eventOuts. This means that if a Script node
has access to a Group node, for example, it has access to (and may
read or write to) any of the Group's children.
Since browsers know whether or not a script
might directly modify other nodes (from the directOutput field),
and because browsers know which nodes scripts may access (from their
fields, events received, etc.), they can determine which parts of
the scene cannot possibly change. And, knowing that, browsers may
decide to perform certain optimizations that are only worthwhile
if the scene doesn't change. For example, a browser could decide
to create texture map "imposters"images of the object
from a particular point of view that can be drawn less expensively
than the object itselffor objects that cannot change. It is
best to limit the number of nodes to which a script has access so
that browsers have maximum opportunity for such optimizations.
Often the same task can be performed either
using a ROUTE or by giving a script direct access to a node and
setting its directOutput field to TRUE. In general, it is better
to use a ROUTE, since the ROUTE gives the browser more information
about what the script is doing and, therefore, gives the browser
more potential optimizations.
Some languages supported by VRML browsers may
allow Script nodes to spontaneously generate events, allowing users
to create Script nodes that function like new Sensor nodes. In these
cases, the Script is generating the initial events that causes the event
cascade, and the scripting language and/or the browser shall determine
an appropriate timestamp for that initial event. Such events are then
sorted into the event stream and processed like any other event, following
all of the same rules including those for looping.
tip
Java, for example, allows the creation of separate
threads. Those threads can generate eventOuts at any time, essentially
allowing the Script containing the Java code to function as a new,
user-defined sensor node.
If you want to create scalable worlds, you
should be careful when creating asynchronous threads. You can easily
swamp any CPU by creating a lot of little scripts that are all constantly
busy. Make each script as efficient as possible, and make each thread
inactive (blocked and waiting for input from the network, for example)
as much of the time as possible.
The Script node's url field may specify
a URL which refers to a file (e.g., using protocol http:) or incorporates
scripting language code directly in-line (e.g., using protocol
javabc:). The MIME-type of the returned data defines the language type.
Additionally, instructions can be included in-line using either the
data: protocol (which allows a MIME-type specification)
or a "2.5.5 Scripting Language Protocol"
defined for the specific language (from which the language type is inferred).
For example, the following Script node has one
eventIn field named start and three different URL values specified
in the url field: Java, JavaScript, and inline JavaScript:
Script {
eventIn SFBool start
url [ "http://foo.com/fooBar.class",
"http://foo.com/fooBar.js",
"javascript:function start(value, timestamp)
{ ... }"
]
}
In the above example when a start eventIn
is received by the Script node, one of the scripts found in the url
field is executed. The Java code is the first choice, the JavaScript
code is the second choice, and the inline JavaScript code the third
choice. A description of order of preference for multiple valued URL
fields may be found in "2.5.2 URLs."
design note
An earlier design of the Script node had a
languageType SFString field and a script SFString field that either
contained or pointed to the script code. Using the same URL paradigm
for the script code as is used for other media types that aren't part
of VRML (images, movies, sounds) is a much better design and had several
unexpected benefits:
- Scripts can be placed either inline (using
the data:, javabc:, or javascript: protocols) or can be placed in
separate files.
- The desire to put script code inline inside
the VRML file led to investigation and the discovery of the data:
protocol proposal (see Section 2.1.4, Data Protocol). That, in turn,
allows any media type (images, sounds, movies, or script code) to
be put inline in the VRML filea feature that was often requested.
- All of the functionality of the MIME and URL
standards can be used with script code (e.g., server-client content-type
negotiation or automatic decompression of script code via the standard
content-encoding mechanisms).
Events received by the Script node are passed
to the appropriate scripting language method in the script. The method's
name depends on the language type used. In some cases, it is identical
to the name of the eventIn; in others, it is a general callback method
for all eventIns (see the scripting language appendices for details).
The method is passed two arguments: the event value and the event timestamp.
design note
Passing the event time stamp along with every
event makes it easy for script authors to generate results that are
consistent with VRML's ideal execution model. If time stamps were
not easily available, then script authors that wished to schedule
things relative to events (e.g., start animations or sounds two seconds
after receiving an event) would be forced to schedule them relative
to the time the script happened to be executed. That wouldn't be a
problem for an ideal implementation that processed all events the
instant they happened, but it could cause synchronization problems
for real implementations, since two scripts triggered by the same
event will be executed at slightly different times (ignoring multiprocessor
implementations, which are possible and could execute scripts in parallel).
There is no way for a Script node to find out
what nodes are sending it events, and no way for it to find out what
nodes are receiving the events it generates (unless it is a directOutput
Script that is directly sending events to nodes, of course). This
was done to restrict the number of nodes to which a script potentially
has access, allowing browsers more opportunities for optimization
(refer back to Section 2.7.5, Scripts with Direct Outputs). Passing
some sort of opaque node identifier with each event was also considered,
and would have allowed scripts to do some interesting things with
events that were fanned-in from several different nodes. However,
that feature probably would not be used often enough to justify the
cost of passing an extra parameter with every event.
The fields, eventIns, and eventOuts of a Script
node are accessible from scripting language methods. Events can be routed
to eventIns of Script nodes and the eventOuts of Script nodes can be
routed to eventIns of other nodes. Another Script node with access to
this node can access the eventIns and eventOuts just like any other
node (see "2.12.5 Scripts with direct outputs").
It is recommended that user-defined field or
event names defined in Script nodes follow the naming conventions described
in "2.7 Fields, eventIns, and eventOuts
semantics."
2.12.9.1 Accessing fields and eventOuts of
the script
Fields defined in the Script node are available
to the script through a language-specific mechanism (e.g., a variable
is automatically defined for each field and event of the Script node).
The field values can be read or written and are persistent across method
calls. EventOuts defined in the Script node may also be read; the returned
value is the last value sent to that eventOut.
2.12.9.2 Accessing eventIns and eventOuts of
other nodes
The script can access any eventIn or eventOut
of any node to which it has access. The syntax of this mechanism is
language dependent. The following example illustrates how a Script node
accesses and modifies an exposed field of another node (i.e., sends
a set_translation eventIn to the Transform node) using JavaScript:
DEF SomeNode Transform { }
Script {
field SFNode tnode USE SomeNode
eventIn SFVec3f pos
directOutput TRUE
url "javascript:
function pos(value, timestamp) {
tnode.set_translation = value;
}"
}
The language-dependent mechanism for accessing
eventIns or eventOuts (or the eventIn or eventOut part of an exposedField)
shall support accessing them without their "set_" or
"_changed" prefix or suffix, to match the ROUTE statement
semantics. When accessing an eventIn named "zzz" and
an eventIn of that name is not found, the browser shall try to access
the eventIn named "set_zzz". Similarly, if accessing
an eventOut named "zzz" and an eventOut of that name
is not found, the browser shall try to access the eventIn named "zzz_changed".
design note
If the Script accesses the eventIns or eventOuts
of other nodes, then it must have its directOutput field set to TRUE,
as previously described in Section 2.7.5, Scripts with Direct Outputs.
EventIns of other nodes are write-only; the
only operation a directOutput Script may perform on such eventIns
is sending them an event. Note that this is exactly the opposite of
the Script node's own eventIns, which are read-onlythe Script
can just read the value and time stamp for events it receives.
EventOuts of other nodes are read-only; the
only operation a directOutput Script may perform on them is reading
the last value they generated (see Chapter 4, Fields and Events, for
the definition of the initial value of eventOuts before they have
generated any events). A Script's own eventOuts, on the other hand,
can be both read and written by the script.
Fields of other nodes are completely opaque
and private to the node. A Script may read and write its own fields
as it pleases, of course. If fields were not private to the nodes
that owned them it would be quite difficult to write a robust Script
node, since other nodes could possibly change the Script's fields
at any time.
ExposedFields of other nodes are just an eventIn,
an eventOut, and a field. The eventIn may be written to by a directOutput
Script, and the eventOut may be read, giving complete read/write access
to the field.
2.12.9.3 Sending eventOuts
Each scripting language provides a mechanism
for allowing scripts to send a value through an eventOut defined by
the Script node. For example, one scripting language may define an explicit
method for sending each eventOut, while another language may use assignment
statements to automatically defined eventOut variables to implicitly
send the eventOut. Sending multiple values through an eventOut during
a single script execution will result in the "last" event
being sent, where "last" is determined by the semantics of
the scripting language being used.
design note
The specification should be more precise here.
To avoid potential problems, you should not write scripts that generate
events from the same eventOut that have the same time stamp. And browser
implementors should create Script node implementations that send out
only the "last" eventOut with a given time stamp, assuming
that the scripting language being used has a well-defined execution
order (which may not be true if languages that support implicit parallelism
are ever used with VRML).
The browser interface provides a mechanism for
scripts contained by Script nodes to get and set browser state (e.g., the
URL of the current world). This section describes the semantics
of methods that the browser interface supports. An arbitrary syntax
is used to define the type of parameters and returned values. The specific
appendix for a language contains the actual syntax required. In this
abstract syntax, types are given as VRML field types. Mapping of these
types into those of the underlying language (as well as any type conversion
needed) is described in the appropriate language appendix.
2.12.10.1 SFString getName( ) and SFString
getVersion( )
The getName() and getVersion()
methods return a string representing the "name" and "version"
of the browser currently in use. These values are defined by the browser
writer, and identify the browser in some (unspecified) way. They are
not guaranteed to be unique or to adhere to any particular format and
are for information only. If the information is unavailable these methods
return empty strings.
2.12.10.2 SFFloat getCurrentSpeed( )
The getCurrentSpeed() method returns
the average navigation speed for the currently bound NavigationInfo node in meters per second, in
the coordinate system of the currently bound Viewpoint node. If speed of motion is not meaningful
in the current navigation type, or if the speed cannot be determined
for some other reason, 0.0 is returned.
2.12.10.3 SFFloat getCurrentFrameRate( )
The getCurrentFrameRate() method returns
the current frame rate in frames per second. The way in which frame
rate is measured and whether or not it is supported at all is browser
dependent. If frame rate measurement is not supported or cannot be determined,
0.0 is returned.
2.12.10.4 SFString getWorldURL( )
The getWorldURL() method returns the
URL for the root of the currently loaded world.
2.12.10.5 void replaceWorld( MFNode nodes )
The replaceWorld() method replaces the
current world with the world represented by the passed nodes. An invocation
of this method will usually not return since the world containing the
running script is being replaced. Scripts that may call this method
shall have mustEvaluate set to TRUE.
2.12.10.6 void loadURL( MFString url, MFString
parameter )
The loadURL() method loads the first
recognized URL from the specified url field with the passed parameters.
The parameter and url arguments are treated identically
to the Anchor node's parameter and url fields (see "3.2 Anchor").
This method returns immediately. However, if the URL is loaded into
this browser window (e.g., there is no TARGET parameter to redirect
it to another frame), the current world will be terminated and replaced
with the data from the specified URL at some time in the future. Scripts
that may call this method shall set mustEvaluate to TRUE.
2.12.10.7 void setDescription( SFString description
)
The setDescription() method sets the
passed string as the current description. This message is displayed
in a browser dependent manner. An empty string clears the current description.
Scripts that may call this method must have mustEvaluate set
to TRUE.
2.12.10.8 MFNode createVrmlFromString( SFString
vrmlSyntax )
The createVrmlFromString() method imports
a string consisting of a VRML scene description, parses the nodes contained
therein, and returns the root nodes of the corresponding VRML scene.
The string must be self-contained (i.e., USE statements inside
the string may refer only to nodes DEF'ed in the string, and non-built-in
node types used by the string must be prototyped using EXTERNPROTO or
PROTO statements inside the string).
2.12.10.9 void createVrmlFromURL( MFString
url, SFNode node, SFString event )
The createVrmlFromURL() instructs the
browser to load a VRML scene description from the given URL or URLs.
The VRML file referred to must be self-contained (i.e., USE statements
inside the string may refer only to nodes DEF'ed in the string, and
non-built-in node types used by the string must be prototyped using
EXTERNPROTO or PROTO statements inside the string). After the scene
is loaded, event is sent to the passed node returning
the root nodes of the corresponding VRML scene. The event parameter
contains a string naming an MFNode eventIn on the passed node.
2.12.10.10 void addRoute(...) and void deleteRoute(...)
void addRoute( SFNode fromNode, SFString fromEventOut,
SFNode
toNode, SFString toEventIn );
void deleteRoute( SFNode fromNode, SFString
fromEventOut,
SFNode
toNode, SFString toEventIn );
These methods respectively add and delete a
route between the given event names for the given nodes. Scripts that
may call this method must have directOutput set to TRUE.
design note
The composability and scalability design goals
put severe restrictions on the Script node browser interface API.
For example, a call that returned the root nodes of the world was
considered and rejected because it would allow a script unrestricted
access to almost all of the nodes in the world, severely limiting
a browser's ability to reason about what might and might not be changing.
Many features that were initially part of the API were moved into
nodes in the scene, because doing so made the design much more composable
and consistent.
The functions that are left are very general
and quite powerful. Several of the standard nodes in the VRML specification
could be implemented via prototypes that used these API calls. For
example, the Anchor node could be implemented as a Group, a TouchSensor,
and a Script that made loadURL() and setDescription() calls at the
appropriate times, and an Inline could be implemented as a Group and
a Script that called createVrmlFromURL(url, group, "set_children")
in its initialize() method.