World Instancing
From Multiverse
| Multiverse Servers |
|
Installing • Installing on Linux • Running • Troubleshooting • FAQ • Release Notes • Updating • JMX Monitoring & Mgmt. |
| Infrastructure |
|
Platform Architecture • Registering a World • Proxy Server • Event Handling • World Manager • Voice Server |
| Messaging System |
|
Perception Messaging • Using Extension Messages • Message Marshalling • Multi-subject Messaging • Message Catalog |
| Object Architecture |
|
World Instancing • Server Object Search • Server Regions • Server Markers |
| Scalability and Performance |
| Reference |
|
File Layout • Property File • Logging • API |
Contents |
Overview
World instancing enables you to build a virtual world from multiple world files. You can use instancing to separate disjoint geography, distribute large player populations, or provide private content areas.
An instance is defined by a world file (.mvw) and its collection files (.mwc). You can use a single world file to create multiple instances. Instances are initialized by a pair of script files; one for creation and one for loading. The world and script file names are collected together in an instance template which is used when creating a template.
There is no perceivability between instances; players, mobs, and objects can only "see" within their current instance. Players can move between instances, but can be in only one at a time. When changing instances, the client clears the scene of all current objects, terrain, skybox, and so on, before loading objects in the destination instance.
Instancing APIs are in the multiverse.server.plugins.InstanceClient package.
Examples in this article are written in Python, but apply equally well to Java.
Instance portal examples in Sampleworld
There are several examples of instance portals in Sampleworld. There are examples of:
- Instancing via object: when the user moves the mouse pointer over the object, a pop-up menu appears showing the instance name. The user can then select it to change into that instance. The barrel with the red decal under it is an example of this kind of portal.
- Instancing via region: moving into the region initiates an instance change. The region named "meetinghouse_region" is an example of this kind of portal.
In both, cases, you must add special properties to the region or object. In World Editor, select the region or object and then edit the NameValue property in the Property View. For more information on setting properties in World Editor, see World Editor - Adding and editing properties.
These instance portals use the InstancePortal example script provided with Sampleworld.
Portal object
The figure below shows the NameValue dialog box for the barrel that is a portal to the Sampleworld video instance.
In this example, the properties are:
| Property name | Example value | Description |
|---|---|---|
| InstancePortal | Right click for video instance | Mouseover text to display. |
| instanceName | video | Name of the instance to which the portal takes characters. |
| instanceFlags | push | Special instance flags: If this is "push" or "pop", the portal manipulates the instance stack. |
| locMarker | spawn | Name of the marker where characters spawn in the target instance. |
Portal region
The meetinghouse_region is an example of a portal region. It transfers users who enter the region into the "bigbrother" instance in Sampleworld. NOTE: The meetinghouse_region is directly on top of the house model, so it is hard to see; you can move the house if you want to see the region in the world view.
For more information on setting properties in World Editor, see World Editor - Adding and editing properties.
| Property name | Example value | Description |
|---|---|---|
| OnEnter | instanceEntry | Specifies that this is a instance entry region trigger. In other words, indicates that this regio is a portal region. |
| instanceName | bigbrother | Name of the instance to which this portal transports players. |
| locMarker | spawn | Name of the marker where players appear in the target instance. |
Creating an instance
To create an instance, create an instance template and optionally an override template.
Defining an instance template
An instance template is required to create an instance. Instance templates are stored in the instance plugin and must have a unique name. The instance template must include a world file and may include an init script and/or load script. The init script is run when the instance is created and the load script is run when a persistent instance is loaded.
In this example, the template uses world file "myworld.mvw" and init script "instance_load.py":
template = Template("my template name")
template.put(Namespace.INSTANCE, InstanceClient.TEMPL_WORLD_FILE_NAME, "$WORLD_DIR/myworld.mvw")
template.put(Namespace.INSTANCE, InstanceClient.TEMPL_INIT_SCRIPT_FILE_NAME, "$WORLD_DIR/instance_load.py")
rc = InstanceClient.registerInstanceTemplate(template);
Log.debug("Registered instance template: status="+str(rc))
File names in an instance template may contain the following variables:
-
$MV_HOME-- the value of MV_HOME environment variable, generally set by the server start script. -
$WORLD_NAME-- the world name, generally set by the server start script. -
$WORLD_DIR-- the world config directory:$MV_HOME/config/$WORLD_NAME, generally set by the server start script.
The template can specify custom properties by adding them to the instance namespace. Instances created with the template will have the same properties. For example:
template.put(Namespace.INSTANCE, "myProperty", "myPropertyValue")
Instance templates are registered with the instance plugin using InstanceClient.registerInstanceTemplate(). Instance templates are typically registered in startup_instance.py which is run once when the instance plugin process is started.
Using a template override
A template override is optional, but is useful to set the instance name (not to be confused with the template name). The template and override are merged to form the instance properties. Use the InstanceClient.createInstance() method to create an instance with a template override. For example:
overrideTemplate = Template()
overrideTemplate.put(Namespace.INSTANCE, InstanceClient.TEMPL_INSTANCE_NAME, "default")
oid = InstanceClient.createInstance("my template name", overrideTemplate)
Log.debug("startup_instance.py: createInstance instanceOid=" + str(oid))
Instance names are optional and need not be unique. You can get an instance OID by name, but if there are multiple instances with the same name, the selected instance is undefined. Instance names are useful when setting a new player's initial location, resolving player location after instance deletion, and moving to standard instances.
An instance is in the loaded state after creation and is ready for immediate use.
Startup instance
The Multiverse server does not create a default or initial instance. Instances can be created or loaded at startup by a "post" script passed to the instance plugin process. By default, the script is named multiverse/config/myworld/startup_instance.py. See the sampleworld multiverse/config/sampleworld/startup_instance.py for an example.
Persistent instances
Instances may be persistent or non-persistent (the default). Persistent instances are saved in the database like any other object. Additionally, the persistent contents of a persistent instance are automatically loaded and unloaded with the instance.
Create a persistent instance as you would other objects, by setting the persistent flag in the object template:
template.put(Namespace.OBJECT_MANAGER, ObjectManagerClient.TEMPL_PERSISTENT, Boolean(True))
Instance persistence must be set when the instance is created. To persist the content of an instance, you must explicitly make the objects persistent.
Managing instances
Once you create an instance, you can load it, unload it, and delete it. Use methods of InstanceClient to manage instances. Do not use the ObjectManagerClient API to manage instances.
Loading an instance
Loading an instance loads the instance object and all its persistent content from the database. When loading is complete, the instance is ready for use. Loading is only available for persistent instances. Use
InstanceClient.loadInstance() to load an instance, providing the instance OID as the argument.
Unloading an instance
Unloading an instance unloads all instance content and the instance object itself. Unloaded objects are removed from memory, but the persistent objects are still stored in the database. An unloaded non-persistent instance is effectively deleted. Use the
InstanceClient.unloadInstance() method to unload an instance, providing the instance OID as the argument.
Unloading and deleting are equivalent for non-persistent instances.
When an instance is unloaded, a SubjectMessage type InstanceClient.MSG_TYPE_INSTANCE_UNLOADED is published after the instance content is unloaded, but before the instance object is unloaded. The message is published as a broadcast RPC and the caller waits for all subscribers to respond. This message can be used to clean-up resources outside the multiverse distributed object system.
Deleting an instance
Deleting an instance deletes all instance content and the instance object itself from both memory and the database.
Use InstanceClient.deleteInstance() to delete an instance, providing the instance OID as the argument.
When an instance is deleted, a SubjectMessage type InstanceClient.MSG_TYPE_INSTANCE_DELETED is published after the instance content is deleted, but before the instance object is deleted. The message is published as a broadcast RPC and the caller waits for all subscribers to respond. This message can be used to clean-up resources outside the multiverse distributed object system. The message subject is the instance oid.
Entering an instance
Moving from one instance to another can be triggered by the server or the client. The proxy server manages instance entry as follows:
- Verify the destination instance exists.
- Despawn the player from the current instance.
- Change the player's location to the destintation instance.
- Spawn the player into destination instance, which becomes the current instance.
Call InstanceClient.objectInstanceEntry() to initiate instance entry, specifying a destination instance OID, location within the instance, and optionally a new orientation. The player's direction is always set to zero after instancing. Specify the location as a marker name within the destination instance.
Restore stack
The instance restore stack maintains a player's previous instance locations. The stack is stored in two properties in the object manager namespace, one for the stack itself and one for the current instance name.
The restore stack is a List of InstanceRestorePoint objects. The bottom of the stack should be a "fallback" location to be used if the upper restore points no longer exist. The character factory must initialize the restore stack with a fallback location.
The proxy server maintains the current instance name. It is used at login time if the player's current instance OID does not exist. The proxy looks up the current instance name to see if the instance was recreated under a different OID. This happens when the world uses non-persistent instances.
To add an entry to the top of the restore stack, specify FLAG_PUSH when calling objectInstanceEntry(). The player's current location (before instancing) is pushed on top of the stack. Optionally, the caller can specify the exact restore location (for example, to restore a player to just outside an instance portal).
You can instance a player to the top (latest) restore point by specifying FLAG_POP when calling objectInstanceEntry(). In this case, the 'instanceLoc' parameter is ignored.
Character creation
Character creation factories must set the player object's initial instance location and configure the instance restore stack.
# Get the start instance for all new players
instanceOid = InstanceClient.getInstanceOid("default")
if not instanceOid:
Log.error("CharFactory: no 'default' instance")
properties.put("errorMessage", "No default instance")
return 0
# Get the player initial spawn location. Create this marker in
# the WorldEditor.
spawnMarker = InstanceClient.getMarker(instanceOid, "playerSpawn")
spawnMarker.getPoint().setY(0)
# Set the player name
override.put(WorldManagerClient.NAMESPACE,
WorldManagerClient.TEMPL_NAME, name)
# Set the player object instance
override.put(WorldManagerClient.NAMESPACE,
WorldManagerClient.TEMPL_INSTANCE, Long(instanceOid))
# Set location within the instance (from the marker)
override.put(WorldManagerClient.NAMESPACE,
WorldManagerClient.TEMPL_LOC, spawnMarker.getPoint())
# Set orientation (from the marker)
override.put(WorldManagerClient.NAMESPACE,
WorldManagerClient.TEMPL_ORIENT, spawnMarker.getOrientation())
# Set persistent flag so player is saved in the database
override.put(Namespace.OBJECT_MANAGER,
ObjectManagerClient.TEMPL_PERSISTENT, Boolean(True))
# Initialize player's instance restore stack
restorePoint = InstanceRestorePoint("default", spawnMarker.getPoint())
restorePoint.setFallbackFlag(True)
restoreStack = LinkedList()
restoreStack.add(restorePoint)
override.put(Namespace.OBJECT_MANAGER,
ObjectManagerClient.TEMPL_INSTANCE_RESTORE_STACK, restoreStack)
override.put(Namespace.OBJECT_MANAGER,
ObjectManagerClient.TEMPL_CURRENT_INSTANCE_NAME, "default")
Login processing
When a player logs in, the proxy server uses the player's last known location and the player restore stack to determine the player's initial location. The following steps are taken:
- Attempt to load object at last known location.
The last known location is an instance oid and point in that instance. This will succeed if the instance is already loaded or it is a persistent instance (a persistent instance is automatically loaded when any of its contents is loaded). The player object load will fail if the player was in a non-persistent instance that has been deleted or the server restarted. - Get the player's current instance name, lookup the oid, change player's current location.
This works when the player was in a well-known, but non-persistent instance. Some instances may always exist (created by server start-up script), but not be persistent. - Attempt to load object at current location.
- Pop the top entry from the player's Instance Restore Stack and change player's current location to match.
If the restore point had an instance name, then the instance oid is taken from instance name lookup. - Repeat from step 3 until the restore stack is empty or the fallback restore point is reached.
Other ways to enter an instance
A player can also enter an instance:
- Because of a server message
- Because of a client event
- Due to moving into a new region
Entry via server message
The server can instance a player at any time by sending an InstanceEntryReqMessage. The InstanceClient.objectInstanceEntry() APIs will do this for you.
Instance entry needs the following information:
- Player OID
- Destination instance OID - specified in the BasicWorldNode or by instance name
- Destination location - specified in the BasicWorldNode
- optional restore stack manipulations
The version of objectInstanceEntry() that accepts an instance name simply looks up the instance oid prior to sending the message.
Restore stack manipulations are specified with the 'flags' parameter. The possible values are FLAG_NONE, FLAG_PUSH, and FLAG_POP. If FLAG_NONE, then the restore stack is neither consulted nor modified. If FLAG_PUSH, then a restore point is pushed on top of the restore stack. The restore point is either the player current location, or a location specified in parameter 'restoreLocation'. If FLAG_POP, then the top restore point is removed from the stack and the player instanced to that location. The destination parameters to objectInstanceEntry() are ignored in this case.
The flag constants can be found on static class InstanceClient.InstanceEntryReqMessage
# roomName = destination instance name # playerOid = player to move # "spawnPt" = marker in destintation instance instanceOid = InstanceClient.getInstanceOid(roomName) spawnPt = InstanceClient.getMarker(instanceOid, "spawnPt") wnode = BasicWorldNode() wnode.setInstanceOid(instanceOid) wnode.setLoc(spawnPt.getPoint()) wnode.setOrientation(spawnPt.getOrientation()) wnode.setDir(MVVector(0,0,0)) InstanceClient.objectInstanceEntry(playerOid, wnode, InstanceClient.InstanceEntryReqMessage.FLAG_NONE)
Entry via client event
You may trigger instance entry with a client extension message. The sample assets include a client-side "/instance" command in Script/MarsStandardCommands.py that sends this extension message to instance the player. You must register a hook in the proxy server for this to work, see Proxy Server#InstanceEntryProxyHook.
The client extension message supports all the options available in the server message InstanceEntryReqMessage.
- instanceName - The destination instance name
- instanceOid - The destination instance oid
- locMarker - Marker in the destination instance, this is the spawn point
- locPoint - (optional) Point in the destination instance (? how is this set on the client)
- orientation - (optional) Spawn orientation, overrides marker orientation
- flags - (optional) One of "push" or "pop"
- restoreMarker - (optional) Marker in the current instance. When flags is "push", push this location on the restore stack (instead of the player's current location).
- restorePoint - (optional) Point in the current instance. When flags is "push", push this location on the restore stack (instead of the player's current location).
- restoreOrientation - (optional) Restore orientation, overrides restore marker orientation
props = {}
props["instanceName"] = "default"
props["locMarker"] = "spawn"
ClientAPI.Network.SendExtensionMessage(0, False, "proxy.INSTANCE_ENTRY", props)
Changing instance when entering a region
You can configure a region to automatically change a player's instance when entered. See: Server Regions - InstanceEntryRegionTrigger
Finding instances
If you know the name of an instance, you can get its Oid via InstanceClient.getInstanceOid()
You can search loaded instances with the Server Object Search API. Use the following search parameters:
- objectType: ObjectTypes.instance
- query: instance of
PropertySearch - selection: instance of
SearchSelectionwith instance property names or the get-all-properties flag enabled.
The search result is java.util.Map objects with the selected properties. SearchSelection flags RESULT_KEYED and RESULT_KEY_ONLY are both supported.
queryProps = HashMap() # Add matching properties to queryProps search = PropertySearch(queryProps) # Get the instance name and instance template name selection = SearchSelection() selection.addProperty(InstanceClient.TEMPL_INSTANCE_NAME) selection.addProperty(InstanceClient.TEMPL_INSTANCE_TEMPLATE_NAME) # Search using the default format instances = SearchManager.searchObjects(ObjectTypes.instance, search, selection) print "1: " + str(instances) # Get collection of SearchEntry selection.setResultOption(SearchSelection.RESULT_KEYED) instances = SearchManager.searchObjects(ObjectTypes.instance, search, selection) print "2: " + str(instances) # Get collection of instance oids selection.setResultOption(SearchSelection.RESULT_KEY_ONLY) instances = SearchManager.searchObjects(ObjectTypes.instance, search, selection) print "3: " + str(instances)
Determining an object's instance
A world node contains information about the location of an object in the virtual world including location (x,y,z), orientation, / direction and current instance OID. To determine an object's current instance:
- Get the world node from the world manager with
WorldManagerClient.getWorldNode(). This returns amultiverse.server.engine.BasicWorldNodeobject. - Call the
getInstanceOid()on this object.

