A Proxied node is a Node that is created as a surrogate for another node. The proxy represents the Node it is a surrogate of, but in another GOM; more specifically in another Area Instance than the actual Node it represents. For example a Node can exist in Area 1 and have proxy nodes in Area 2, and 3. In this way, scripts in areas 2 and 3 can interact with the proxy of the node in 1 through it's surrogate proxy nodes in their GOM. As illustrated by this example, a node can have multiple proxies.
The most common types of nodes that have proxies are related to player characters. And, in fact, there is special handling for these so that their replication groups can stay intact as they move between areas. Creatures and NPCs are also commonly replicated, but you can script any sort of other node to also take advantage of proxies. By far the most common reason to do this is to provide a Seamless World experience for the player.
- Proxied nodes share the ID of the actual (source) node
- Proxied nodes do NOT persist
- Proxied nodes may share their source node's class, or may be created from another class as specified by the class definition in the DOM. Fields on the source node can be mapped to fields on the proxy node and are kept up-to-date through Replication.
- Any node can have zero or more proxy nodes. There can be only one proxy node for a given real node in any Area Instance, and a proxy node cannot exist for its real node in the same Area Instance.
- When a proxied Node is established, the only methods that can be run on it are those which have the ProxyLocal or ProxyForward modifiers. Attempts to run any other method from the destination process, will generate a script error. These modifiers determine on which Area Instance the HSL code actually executes.
- When working with proxy nodes, code is generally structured to function in an asynchronous pattern. While local field values may be read (or even written to), there is always a chance that the data has changed on the source node but has not yet been replicated to the proxies. Non-time sensitive local field data may used, but any fields that are replicated should only be changed on the original node. This is easy accomplished with ProxyForward methods.
Proxied Class Design
The diagram depicts the creation of a lightweight class that is intended to be used when proxying the class Foo. Lightweight Foo would include all of the fields that are intended to be replicated to the proxied node as well as any fields that are intended for use locally whether source or proxied node. Foo's class definition would then have the
Destination Class (Server) set to be Lightweight Foo. Foo inherits Lightweight Foo so we can write code that is agnostic to whether it is working with a proxy or the source by casting the node to be Lightweight Foo.
Specify the Instantiation Class for a Proxy Node
The class used when instantiating a proxied node is determined by the base class of the source node as specified in its DOM definition. The DOM Editor is used to set the
Destination Class(Server) property for your class.
For classes that are GLOMmed onto the original node, their destination class is GLOMmed onto the proxied node just as they are GLOMmed on the source.
The initiation of proxying for a source node (and/or its hierarchy) to one or more destinations is tied to the underlying mechanics of server-to-server replication.
Mechanically the following conditions must exist for server-to-server replication to function:
- one or more classes from which the source node is composed are defined as replicating (i.e. their DOM definition has a Destination Class(Server) set
- the source node has been added to a replication group
- one or more destination area instances have been specified by some external system that has decided the destination needs a proxy
Controlling what is proxied and when the proxying is initiated is fully under the control of HSL systems. The MMO Foundation Framework implementation of Seamless world 1.0 initiates proxied based on the awareness of an entity placed in the default Spatial Awareness System by a Seamless area link.
The MMO Foundation Framework's implementation of the account node includes a method to retrieve the default replication group used for introduction of the account hierarchy to observing clients and areas. By default, the replication group includes the nodes: _playerAccount (primary), _playerCharacter, _characterAppearance and the _ACCController. The following code snippet results in the proxying of those nodes to the specified destination area.
var repGroup = account._getReplicationGroup( false ) repGroup._addServerDestination( areaID, instanceID )
- See also: Function/method modifiers
Methods called on a proxied mode must have a specific keyword modifier, to indicate whether the method is to be run on the target area instance server process (where the actual proxied node is located), or on the source area server process (where the original node is still located). Relevant modifiers are:
- ProxyLocal: Methods with this modifier will be run locally on the Area Instance server process where the call occurs.
- ProxyForward: Methods with this modifier will "forward" to the source node for processing by the Area Instance server where the source node is, and in fact will operate on the source node (i.e., the
mevariable will reference the actual proxied node not the proxy).
- If the original node moves to another area, and a ProxyForward method is called before receiving notification of the move, the proxy call will arrive at the old area. Typically with normal motion, there will still be a proxy node in the old area which will forward the call on to the correct server. If the original node is gone, due to teleporting or otherwise unloading, the forwarded method call will be dropped.
Asynchronous Considerations for Proxied Nodes
The decision to use proxied nodes in your game design imposes certain implementation decisions upon your game systems. Specifically, for anything operation that should work with both real nodes and proxied nodes, the interface you script must be asynchronous. This is because a methods called on the proxy will forward to the real node in its GOM. The results of that method call will not be reflected on the proxy immediately as there is a round-trip for any field updates, and so forth.
You can change fields on a proxied node, however these changes do not reflect to the real node or any other associated proxies. In fact, if the field is replicated from the source node, and it updates, then any change you made on the proxy would get clobbered by the replication update. Sometimes it might make sense to change a field on a proxy node and then use proxyForward method call to change it on the real node too. This technique can be useful in some situations.
No changes to fields on the proxy node are persisted, regardless of the write strategy on the field. Only the source node persists.
Asynchronous vs Synchronous: setHealth()
Your game may have a method setHealth() that is used by combat to apply the result of changes to health based on attacks or healing received. When an attack occurs, you might set the character's health the
current value - X.
setHealth() the Wrong Way
method setHealth( newHealth as float ) me.currentHealth = newHealth .
This is not the correct way to implement this method for a game that uses proxies.
- If you were to call set health on a proxy you probably used getHealth() first to the the current health. Using a getHealth() method on a proxy is problematic because it may not be the correct value, remember the authoritative value is stored on the source node (which is located in another GOM). Writing code
newHealth = character.getHealth() - 10while working with a proxy could potentially produce the incorrect value when called on a proxy node because its current value may be out of date.
- If you set the field directly the value will be overwritten when the source node sends an update to the field.. Also, nothing notifies the source node that a change was made. This would result in changes made to proxied nodes being ignored by the authority (the source node).
- Setting an absolute health value can not work because proxied nodes in other GOMs may also simultaneously be setting the absolute value to a different number.
All of these issues demonstrate why we need to perform the adjustment in an asynchronous matter that is communicated to the authoritative GOM containing the source node.
setHealth() the Right Way
proxyForward method adjustHealth( value as float ) me.currentHealth = me.currentHealth + value .
First you may notice that we have renamed the method, instead of setting absolute health we are now adjusting health by a value and consequently have named the method adjustHealth(). The
proxyForward modifier tells the engine to package up the call and forward it (asynchronously) to the source node when called on a proxy. Once forwarded, we are now adjusting the node in the GOM that is authoritative and we can add the specified value to the source node's currentHealth field.
Once currentHealth is modified, we depend on replication to send out notification to all of the proxies to change their locally cached value.
Send a Remote Procedure Call to All Proxies
function sample(n as NodeRef, sampleParam as Boolean) call serverReplicas n.SampleMethod(sampleParam) .
The class methods script for the class from which the proxy was instantiated would need to have implemented a method marked as both remote and proxyLocal.
remote proxyLocal method SampleMethod( sampleParameter as string ) .
Player Reference Tokens
Player Reference Tokens enable communication to clients other than ones in the current area. This is used extensively for seamless operation, and has been automated for the common case of replicated account nodes. So, as long as a player's account node is being replicated to an area, that area can send chat, remote calls, and replicate information to that player. Script systems that maintain Player Reference Tokens should continue to work independent of this.
external function IsProxyNode( node as NodeRef ) as Boolean // Returns a list of arrays of ids. first element in array is the area number, second element is the instance id // Non replicated nodes will return an empty list external function SourceAreasOfNode(node as NodeRef) as List of Array of ID