HSL for Programmers
This page is an introduction to the HeroScript Language and HeroEngine development environment. It is aimed at experienced programmers and scripters who want to dive right into what makes HSL different from what they already know.
For easier "step by step" tutorials, see:
- How to write a script
- An easier "step by step" tutorial that creates a client-side script
- Creating server scripts
- Tutorial on how to create a simple command script, which runs on the server instead of the client
- Do's and Don'ts of HSL
- Tips from programmers on effective script writing
For this information in Video format:
- We've recorded a number of videos from our training sessions. The Video Series "HSL For Programmers" comes in 3 parts, and assumes you have working knowledge of the fundamentals of programming languages.
Introduction
At a Glance:
- Powerful, 1st class programming language
- Object Oriented / Procedural
- Used on Client & Server
- You make your game in HSL (not C++)
- Unique Dynamic Data Data Model
- Fault Tolerant (scripts don't crash things)
- Full IDE-like support right in HeroBlade
- Real-Time, changes are immediate
HeroScript is a powerful high-level scripting language with a light-weight syntax. It shares the simplicity of expression of languages like Visual Basic, but with even lighter syntactical sugar. Yet it is also a fully object-oriented language, with powerful OO capabilities like multiple inheritance. Uniquely, a powerful capability of HSL's data model is that instantiations of classes (called Nodes) can dynamically change their type at run-time by appending new classes.
There are many other unique features of HSL, such as the separation of the data type system from the language itself (the Data Object Model), and a very unique system for managing the relationships between objects (called Associations).
HeroScript is fast to work with. Changes made to scripts are real-time and live. There is essentially zero compile-link-deploy-restart time involved in scripting. Compiling is instantaneous, and updates are reflected to all servers and clients immediately.
HeroScript is also fast. It is designed for high-capacity MMOs. But no language can guard against poor algorithm design, so profiling tools are also included to help you find performance bottlenecks and fix them.
HeroScript Everywhere
It is important to understand that HeroScript is a first-class development language. In HeroEngine, you implement your entire game in HeroScript (although you can use the plugin architecture or direct source code access where necessary for C++ efficiency). If, in the past, you are have worked with game engines where scripting did not exist, or was simply a way of implementing non-critical sections of the game (like UI or simple quests), then this will take some getting used to. In essence, you'll want to develop your entire game using HeroScript and then, only if necessary, resort to source code or plugins for anything that might require special optimization or capabilities not possible with HeroScript alone.
The Big Differences
First, lets discuss some big-picture issues about HSL that are significant to how you work with the language. Some of the things here may be somewhat unusual for a programming language, and others are just familiar variants.
Client and Server
The client and the server have independent script databases (and DOMs, see below). You explicitly create a script to exist on the client or the server.
Object Oriented and Procedural
HSL is both an object oriented langauge and a procedural language. HSL supports the notion of classes (w/ multiple inheritence) with methods as well as just plain old functions. Like C++ you can mix the two freely.
An instance of a class in HSL is called a Node (in other langauges, this is sometimes called an Object). One extremely powerful capability of HeroEngine is that in addition to the class the Node is instantiated from, a Node can mix-in other classes at run-time (this is called glomming a class). In this way, new capabilities can be added to an existing Node.
OO & Procedural? Why Both?
Often we are asked why we have both an OO model and a procedural model. It is worth noting that object-oriented methodology and procedural both have their places. In particular, a MMO is a great example where both paradigms have their advantages:
One would typically use an Object Oriented approach when designing systems, game mechanics, abilities, items and so forth. These concepts are more easily expressed in an Object Oriented design. Especially when considering the power of polymorphism.
But MMOs also require a second kind of scripting that is better done procedurally. For example, a typical MMO will have a large inventory of quests or missions. These often have a lot of one-off special case scripts for special events, triggers and other sorts of actions that occur. In an OO methodology, one would have to create a class to hold the method(s) for these operations. But because of the special one-off nature of these, this leads to a real pollution of the class name space. You essentially end up creating dummy classes just as a holder for a method and not for storage of information. Ick. So by using procedural scripting (functions instead of methods) you can more naturally express these special case scripting needs.
This is just one example of how HSL is designed with precise needs of an MMO in mind (as opposed to a more generalized language like Python).
Data Object Model
Most programming languages allow you to define the data model of your program in code such as in C++ with the class or struct statement. In HSL, however, the classes, fields and other data definitions are actually defined separately from the language by use of either DOM related CLI Commands or the DOM Editor interface.
There is a separate DOM for the client and server. Often the client has similar classes to the server, but they need a lot less fields. For example the server will know a lot about items in the game, but the client only needs to know enough to render them (or their icons). In fact, full details about the items may be a client<->server exchange that only happens when needed (otherwise every login might produce too much network traffic). So that you can code your game in the optimal manner, these two DOMs are kept separate.
The Data Object Model is dynamic. Anyone (who has permissions) can edit the DOM at anytime. Changes to identifiers are reflected instantaneously in all scripts (except those opened in an editor at that moment). So if you change a field's name from "Bob" to "George", any script that directly references it will be correct when next opened. Most changes that might cause a conflict or break a script are detected by editing the DOM and will be aborted.
Real-time editing of the DOM lets you build out your game data model without having to worry about database schema changes or anything of that nature.
It is important to note that the seperation of the data object model from the language itself has a lot of productivity ramifications. For example, in the real-world of MMO development, design often goes through many interative revisions during the development process. Say, for example, half way through implementation the design team decides that "enhancements" need to be renamed "foobars". Yet you have tons of classes and fields already named "enhancement-this" and "enhancement-that". You can choose to try and refactor all this (which is error prone and very time consuming) or just use the new names from that point forward which creates code maintenance problems. But with the DOM editor, you can just rename the fields or classes and their usage is updated in all scripts dynamically. No recompiling, no search-and-replace errors, just done.
This, again, is another example of how the HeroEngine's approach is focused on the real-life practical problems of MMO development with unique solutions.
To translate the terminology:
- A DOM is a collection of data structure definitions: your Class, Field and Enum definitions.
- A Node is an instantiation of a Class. In other languages this might be called an Object.
- A GOM is a collection of Nodes. The Nodes in a GOM exist in memory and/or the database (and because this is a multiprocess environment there is a lot of different "in memory" situations).
- There is a client DOM and a server DOM. Likewise there are client and server GOMs (technically, each area server is has its own GOM).
- The DOM also contains defintions for things that have no direction correlation in languages, like Assocations.
- (Note that there is no such thing as a DOM Node)
- There are special types of Nodes called Prototypes, which are like global templates for Nodes. This also has no equiv in other languages.
Flat Name Space for Identifiers
Probably the most unusual aspect of HSL's data model is that identifiers are in a flat name space. Therefore, if you have a field named "Bob", there is exactly one and only one definition of that field of that name. And it means the same thing no matter what class it may show up in.
In most programming languages, you would expect that a class Foo that has a member named Bob and class Bar that also has a member named Bob, that these two might often mean different things and certainly represent different data storage. But in HSL they are the same. In fact, if you have a Node that is of class Foo and then you glom on class Bar (remember both have the field Bob), the resulting Node will still have one and only one Bob field, not two.
Although this may seem unusual, it does serve some useful purposes. First, a field has a very well defined meaning; one classes' Bob is not different than another class. Further, since Nodes can mix-in classes at will during runtime, this helps keep everything straight syntactically and on a practical level.
The only major change to your programming practices is that this forces you to use more descriptive names for your fields and other data definitions. No longer is it sufficent to make a field named "Count"; instead "InventoryCount" may be what you need to define. However, if a field of name "Count" serves your purpose everywhere that might be used (and having one and only one on a Node is proper) you can use such a field name without problems.
Classes
Classes are a grouping of fields. Each field can be of a particular type, and HSL supports a wide variety of Data Types. Classes may inherit from more than one parent class. Keep in mind that if the same field is duplicated in the hierarchy of classes, it actually results in one and only one instance of that field on the instantiated Node. So if class Foo inherits from class Bar and both have the field Bob, that field will exist exactly once on the resulting Node instantiated from either class Foo or Bar.
Class Methods
All methods for a particular class exist in the script named {ClassName}ClassMethods. So, if you have a class named "Foo" then the methods for that class would be located in the script named "FooClassMethods".
Array Indexing
Array indexing is 1 based. Most langauges are 0 based, but because HSL was designed to be taught to non-programmers (people who are use to counting things starting with 1 instead of 0), we choose to go with 1. So bob[1]
is the first element of array bob, and bob[0]
is not legal.
The me
Node
HSL has a special me
node that is similar to the this
variable in C++. One important distinction though, is that me is available outside of class methods. This is described in more detail below.
Exposed Functions
A library of functions exists to query information, or manipulate the client or server. Most are common to both the client and server, but some are unique. For a list of commonly used functions, see: Function Index
The Integrated Development Environment
HSE2 is HeroEngine's script editor. It is built directly into HeroBlade
Invoking
With HeroBlade open and focus in the render window, hit CTRL-H. Alternately, open HeroBlade's Organizer panel, and double-click a script to edit. You can also open the editor from HeroBlade's dropdown menus.
Opening a Script
As mentioned, scripts can be browsed via the Organizer panel, however when you know the name of the script you wish to open, hit CTRL-SHIFT-S for server scripts or CTRL-SHIFT-C for client scripts, and start typing the name of that script.
Language Fundamentals
Statements
Statements don't require any special character to indicate their end, except for multi-line code block, as described in that section below.
Output
The external function println()
provides very basic functionality for printing and exists on both the server and the client.
println( "This is a message." )
When used in a client script, messages are printed to HeroBlade's Console panel (a.k.a. Logging from the Panels dropdown menu). In server scripts, the message is sent to the Chat panel on the hsl_debug channel.
On the server you can also use the MsgPlayer()
external function if you know the account ID of the player to message.
MsgPlayer( accountID, "", "This is a message." )
Note that the second parameter is the channel name that the message should be displayed on, but can be left empty for the Default channel.
Ending Semicolons
Many languages, like C, C++, Java, C# etc. end statements with a semicolon. Since this is so popular, HSL statements can also end with a semicolon. These semicolons are simply ignored.
So, these two statements are identical:
println("Hello World!") println("Hello World!");
Messaging
While println
and MsgPlayer
functions support simple string output, more complicated messaging is handled via Remote Calls or Replication. Remote calls are used to execute script code in a destination process. Replication maintains copies of nodes in destination processes and provides change notifications. Note by their nature, remotecalls and Replication function as asynchronous operations.
See also: Inter-Process Communication
See also: Asynchronous considerations
Declaration
HeroScript Language (HSL) is strongly typed, which means that variables need to be declared with their type:
count as Integer location as Vector3 message as String myItem as NodeRef of Class InventoryItem questTable as LookupList indexed by ID of NodeRef of class QuestSpecifications
Local variables follow the same scope rules as other languages (they exist within their code block and hide identical identifer names that might exist outside of their code block). The exception being that it is a compile error to try to hide a variable within the same function.
Assignment
Assignment is permissible during declaration:
count as Integer = 1
Or at anytime thereafter:
count = count + 1
Note that both operators, = and ==, are valid for both assignment and equality testing, because their context will always be well-defined.
HSL does not allow for multiple assignment.
Variable Type Inference using VAR
HSL support intrinsic variable type inferencing with the var
keyword. This is a convenient shortcut for declaring variables with complex types. So something like this:
macroMap as LookupList indexed by ID of List of String = Macro:GetMap( myID )
can be written as:
var macroMap = Macro:GetMap( myID )
The var
keyword tells the compiler to declare macroMap
as whatever type is returned by the function GetMap()
in the script Macro
.
Note: This is similar to the feature that is also found in the upcoming 3.0 specification for the C# language.
The var
keyword, in this context means that the variable is typed based on the context. It does not mean "varient" as in Visual Basic; the variable is still strongly typed, and var
only works when the type can be identified by the compiler at compile time.
Other examples:
var ThisWilLBeAnInt = 5 var IAmGoingToBeAString = "Hello World!" var ThisWillEnsureIAmAFloat = 4.5 myItem as NodeRef of Class InventoryItem var IWillAlsoBeANodeRefOfClassInventoryItem = myItem
This is a much more convenient way of specifying the type of an identifier. Also, it helps a lot during refactoing since the type is inferred by assignment.
See also: Data Types
Auto-Conversion
HSL has no variant type, so makes extensive use of auto-conversion for scalar types to facilitate ease of use and readability. Most scalar types autoconvert to String and vice versa.
count as Integer = 5 myString as String = count println( "myString = " + myString )
The following also would have worked because of auto-conversion:
var count = 5 println( "myString = " + count )
Additionally, NodeRef and ID autoconvert between each other.
See also: Auto Conversion
Code Blocks
Code blocks require no special character to start (always being indicated by a keyword), but are ended with a period on a line by themselves:
if count == 1 println( "You're all alone." ) .
While code blocks are indented by convention, this is not enforced in the editor. Note that the TAB
key in the editor inserts 2 spaces.
Flow Control Statements
The following are common looping and branching logic statements.
if count == 1 println( "You are alone." ) else if count == 2 println( "There is someone nearby." ) else println( "You are not alone." ) .
Note: =
is the same as ==
. Allowing for ==
is a convienence for C++, Java and C# programmers.
when myInteger is 1 // do option 1 . is 2 // do option 2 . default // give error feedback . .
Note: There is no "fall through" on when
as there is on the switch
statement in C++.
var myIntegerList = NonExistentUtil:GetAList() foreach item in myIntegerList println( "The next integer is " + item + "." ) .
Note that foreach
implicity defines the "iterator" variable type, which is item
in this example. The scope of item
is limited to the foreach
statement. This is a convienence that makes foreach
statements much simpler syntactically.
var nameToNumberMap = NonExistentUtil:GetThatLookupList() foreach key in nameToNumberMap println( "Hello, " + key + ". Your number is " + nameToNumberMap[key] + "." ) .
Note that key
did not need to be declared, was auto-typed to the lookuplist's key type, and only exists in the scope of the foreach
statement.
loop i from 1 to myList.length println( "Found " + myList[i] + "." ) .
Note that i
does not need to be declared, is auto-typed as an Integer, and only exists in the scope of the loop
block.
loop f from 0 to 1 by 0.1 println( "f=" + f ) .
Note that f
does not need to be declared, is auto-typed as Float, and only exists in the scope of the loop
block.
var isCool = TRUE while isCool isCool = DoSomething() .
Break and Continue
In order to break out of any loop, use the break
statement.
var myIntegerList = NonExistentUtil:GetAList() foreach item in myIntegerList if item == 5 break . .
In order to end the current loop cycle and reprocess the loop conditional, use the continue
statement.
var myIntegerList = NonExistentUtil:GetAList() while myIntegerList.length > 5 DoSomething( myIntegerList ) if CheckSomething( myIntegerList ) continue . DoSomethingElse( myIntegerList ) .
Nodes
Nodes are Class instantiations. To instantiate a class, use the CreateNodeFromClass()
function. This is similar to new
in C++/C#/Java/etc.
myNode as NodeRef of Class MyClass = CreateNodeFromClass( "MyClass" )
Fields and methods on a node are accessed with dot notation.
var count = count + myNode.total myNode.DoCoolThing("Hello World!")
Note: There is no concept of constructors or destructors in HSL as there is in languages like C++.
See also
Class Reflection
To determine Class of a Node, use the where
statement and the kindof
or exactly
operator:
where myNode is kindof MyClass // do MyClass-specific operations on myNode . is kindof YourClass // do YourClass-specific operations on myNode . is exactly TheirClass // do TheirClass-specific operations on myNode . default // do whatever is done with unknown types . .
There is also an inlined variation of the where
statement:
where myNode is kindof MyClass // do MyClass-specific operations on myNode .
The where/is
structure not only allows you to operate on different code paths based on the class of the node, it also type-casts the variable inside that code block. This means that for the scope of that code block, the variable is now of class type indicated by the is
block.
The is exactly
clause works the same was as is kindof
but ignores inherited classes.
Recall that nodes are derived from a class, but that class can have parent classes and, furthmore, nodes can have other classes "glomed" on to them at run-time. The where
construct makes it easy to deal with the many-classes nature of nodes.
The me
Node
A me node is a special variable which is sometimes set to refer to a particular node. The way in which this is used, depends on whether it is being done with functions or methods:
- In Functions, the me node is sometimes set as part of a callback
- In Methods, the me node is the node instance that the method was called on.
ME in functions
When script functions are called, the me
is sometimes used. It is not typed by class, so working with it almost always requires type checking with the WHERE
statement. The following roughly outlines the situations when the me
node is set:
- When a trigger fires on the server, the trigger asset instance that is triggered uses the
me
node to reference itself in the callback to its script. - On the client side, in all camera callbacks the me node will be set to the camera's node ID
- When a function in a script is called indirectly through a ScriptRef field on a node,
me
will be set to that node in the scope of the called function. For example, in a call to the below function, theme
node will be set to referencemyNode
.
myNode.someScriptField:ASharedFunction()
ME in methods
When a method is called on a node, the me
node is always the node the method was called on. In that method, the compiler knows it to be of the class the method belongs to (but no other class). So, for methods, me
is like the "this
" keyword in C++ or C#.
Example:
method Bar() println("Bar") . method Foo() println("Foo, will now call Bar()") me.Bar() // Assumes this .
See also: System nodes
Associations and Node Hierarchies
HeroEngine features an Association Engine which handles keeping track of how nodes are associated with each other. For example, what items are in your inventory, or what characters are the member of a guild. Although you can maintain such lists in many other ways (arrays, lookup lists, etc.), the Association Engine provides a great deal of power that these other techniques do not. Which you choose to use depends on the particular situation.
An Association is particularly useful because it is an independent data structure. For example, if you associate node A with node B, it is bi-directional. So B can know it is associated with A just as easily as A knows it is associated with B (and exactly what type of assocation). Furthermore, if either node A or node B were to be deleted, the assocation simply goes away; no dangling pointers or other problems common in other languages.
An association is a complex data type that has the following attributes:
- Source
- Type
- Target
Source and target can be thought of as parent node and child node, respectively. The association type is simply a string identifier that describes the relationship between the source and target nodes.
Associations come in two types, hard and soft.
Hard Associations
Hard associations are used to define node hierarchies that delineate ownership of children node(s) by a parent node. Hard assocations always describe one-to-one or a one-to-many relationships, and are always exclusive of each other.
Hard associations are used by the server to identify which nodes in a hierarchy are to be loaded when a given parent node is loaded.
Soft Associations
Soft associations are used to define all non-ownership relationships between nodes. They can describe one-to-one, one-to-many, many-to-one, and many-to-many relationships. Soft associations may or may not be grouped to provide for exclusivity with other association types.
Querying Associations
The external function QueryAssociation( source as NodeRef, type as String, target as NodeRef )
returns a list of assocation containing all associations that match the specified parameters. Up to two of the parameters can be wild-carded by specifying None
for the source/target, and/or an empty string (""
) for the association type. Some examples:
The following will return a list of all associations with myNode as the source and myAssociation as the association type, thereby allowing you to iterate through all the targets:
QueryAssociation( myNode, "myAssociation", None )
The following will return a list of all associations of the myAssociation type between any pair of nodes:
QueryAssociation( None, "myAssociation", None )
The following will return a list of all associations in which myNode is the source.
QueryAssociation( myNode, "", None )
There are other specialized functions for common queries. For more information, please see the section on Associations.
Getting Root Nodes
On the client, the top-level node for all instantiated nodes is accessed via the system variable:
world as NodeRef of Class world_anchor = SYSTEM.INFO.WORLDANCHOR
On an area server, the top-level node for all instantiated nodes is accessed via the GetRootNode() function:
root as NodeRef of Class AreaRoot = GetRootNode()
See also: Associations
Procedural or Object Oriented?
HSL started as a procedural language and has added features of object oriented programming over time. Consequently, HSL is capable of expressing both styles of programming allowing the use of whichever style is most appropriate for your needs. Most callbacks from the C++ engine into script code are procedural.
Procedural Coding
As you would expect, calling a function procedurally requires the compilier either being able to identify the script in which the function is located or the scripter explicitly declare the script in which the function is located.
Calling a Function in a Script
Assume you wish to call a function Bar located in the script Foo.
Calling a function in another script directly via the script name.
Foo:Bar()
Calling via a scriptref.
// Assume you have a scriptref variable ( s ) referencing the script Foo s:Bar()
Note that a colon(:) is used to make function calls.
Calling a function located in the same script.
// Note because the function is located in the same script, you may omit the script name and : Bar()
Calling External Functions
External functions communicate directly with the C++ game engine, as such you do not need to specify a script to call them.
var nodeAsString = MarshalNode( myNode )
Note all external functions are declared in the _ExternalFunctions script.
See also: User Functions
Object Oriented Coding
Unlike most object oriented languages you do not define your data object model (i.e. the class and fields) in your object's code; in other words there is no "class definition" syntax in HSL. Instead, these things are defined in a seperate database called the Data Object Model or DOM. You create and edit these data type definitions using the DOM Editor in HeroBlade.
Class methods of a particular class are written in a script named <ClassName> + ClassMethods. For example, a class Foo would look for a script named FooClassMethods.
Assume your class is named Foo and it contains a method named baz. Assume you have an object named obj which is of class Foo.
HeroEngine's DOM supports class inheritance and multiple inheritance at the field/class level, but requires you resolve method conflicts either by explicitly declaring which parent class's method should be called or by avoiding structures where you get the diamond pattern of inheritance.
Calling an Object's Methods
obj.Baz()
Note that methods are called using a period(.) between the object and the method name.
See also: Using Class Methods
Me, the new This
In C++ the current object is referenced in a Method by the keyword this
. In HSL, the equivelent is me
:
me.LopOffHead(true)
Function/Method Signatures
Signatures are made up of one or more keyword modifiers preceding a name, with arguments offset by () and optionally specifying a return type.
<keyword(s)> + name + (arg as String, intArg as Integer...n) + <as ReturnType>
public function DoStuff( arg as String, intArg as Integer ) as boolean return true .
Arguments may be passed using AS, REFERENCES, COPIES with the normal provisos that unnecessary copying is slow.
Function modifiers
- Main page: Function/method modifiers
- (none)
- Default for functions, meaning functions which may only be called from within the same script
- Public
- Functions may be called by other scripts without any particular restrictions.
- Shared
- Similar to public, but functions live in a shared signature space and have an expected signature, which is checked at compile-time.
- Remote
- Functions or methods which handle calls from other servers made via Remote Call
- Untrusted
- Similar to Remote, but indicates functions or methods which may receive a remote call from the client. Because you can never be sure if the client is hacked or not, all functions marked as untrusted have an implicit contract to validate the parameter data, and even the fact that they are being called at all.
- Unique
- Methods which exist in one and only one classMethods script. This is useful for methods you do not wish to allow child classes to override (similar to
sealed
in C#). - ProxyLocal
- Used in conjunction with the Replication system to mark methods which, if run on a proxied node, should be handled in the local GOM
- ProxyForward
- Used in conjunction with the Replication system to mark methods which, if run on a proxied node, should be forwarded back to the source node for handling in that area's GOM
See also
Serialization
It is often useful to move data from one server to another and a server to the client. HSL supports a number of methods for serialization.
- Replication (primary method)
- Marshalling
- External Functions
- Language support
- Marshal Utils script
Replication
HeroEngine has a fully featured Replication System for communicating data between servers and the player client. Replication is the primary means of communicating game data to the client.
External Functions - Marshal
There are a number of external functions that support marshaling a node or prototype. The process of marshalling serializes the fields ( by name ) on a node and the data those fields contain. The serialized data can then be unmarshalled onto a node in the destination. As a result of marshalling working by field name, the source and destination nodes do not necessarily need to be of the same class. For fields not present in the destination node, unmarshalling either ignores the extraneous data or errors (depending on what you prefer).
Marshalling a node.
n as noderef of class Foo = someNode var marshalStr = MarshalNode( n )
Marshalling a prototype.
var marshalStr = MarshalPrototype( getPrototype( "myProto" ) )
Unmarshalling to an existing node.
aFooNode = UnmarshalNode( marshalStr, true )
Required Utility - MarshalUtils
While you do not always want or need to reconstruct the node in the destination GOM, when you do you need additional information such as the class hierarchy. One of the Required Utility scripts is the MarshalUtils script, it has a variety of functions used to marshal a node including marshalling only selected fields as indicated by the classes on the node ( via their classMethods scripts ) or appending additional fields from templated data.
Unmarshalling using the MarshalUtils script supports recreating a node using the class hierarchy data stored in the serialized form created using the marshal functions from the utility.
Language Supported - Marshal Variables
Frequently the data with which you are working is stored in a variable rather than a node, rather that forcing you to create a node and copying the data HSL supports directly serializing a variable. Once serialized, you can unmarshal into a variable or a field that has the same type. This functionality supports arbitrarily complex fields such as list of class Foo
.
Assume myVar is a variable of type list of class Foo.
MARSHAL myVar TO marshalStr
newVar as List of class Foo UNMARSHAL newVar FROM marshalStr
See also: Marshaling
Data Storage
There are a variety of methods supported by HeroEngine for persisting data, as in any language there are trade-offs that must be considered when choosing one method over the others. A brief analysis of the different methods is detailed on the linked page and our staff is available to discuss which particular method(s) are suited to a given task.
See also: Data Storage
Client / Server Communication
The client and the server can communicate in several ways. The low-level capabilities that allow for this are remote calls and (in version 1.22 and later) replication.
Remote calls allow a server to call a function or class method on another server, or on the client. The client can also call a script function or class method on the area server it is connected to (but only those functions/methods marked as untrusted). Note: Because of the reality of hacking and spoofed-clients, it is important for the server to verify any remote calls it receives from a client.
Replication, new in version 1.22, is a mechanism whereby nodes in one GOM (on the server) can be replicated (have a proxy) on attached clients which are handled by a different area server. Updates to fields on these nodes are transmitted based on a prioritization strategy (allowing you to take advantage of bandwidth management features). Data can be replicated in both directions, and even between area instances.
More information
Appendices
Appendix 1 - OO Programming: Define a Class and Execute Its Code
Overview
In HSL, the data object model (classes, fields, enums, etc.) are defined independently of the scripting language (HSL). This is different than most computer languages, but gives HeroEngine it's distinctive power and real-time collaborative flexibility. The DOM can be manipulated with the CLI, but it is much easier to use the DOM Editor GUI.
This section will walk you through the steps of defining and using a new class.
Defining your Data Model
Open the DOM editor from HeroBlade's HeroScript menu, which offers two separate panels one for the server DOM and one for the client DOM.
- Toggle the Read-Only checkbox off.
- Use the DOM Selector to choose the Client DOM
- Select the Classes Type Definition
- Click the New Class Button, name your class something unique
- Select Data as your Archetype, almost all classes created for scripting will use the Data Archetype
- Now create a new string field, name your field something unique (all identifiers must be unique)
- Now add your string field to your class
Having followed these steps should leave you with a class which has a single string field in it. In this particular example I have created the class ObjectOrientedDemo.
Create a new Class Methods Script
Once you have a class defined using the DOM, you need now to create a script that follows a specific naming convention so that HeroEngine knows where to look for the code that belongs to your object. The name expected is NameOfYourClass + ClassMethods. That means the name of my ClassMethods script will be ObjectOrientedDemoClassMethods.
- Create a clientScript using the HeroScript editor, name the script NameOfYourClass + ClassMethods
- Paste the following code as a starting point
method SetObjectOrientedDemo() me.ObjectOrientedField = "This was neat on the client." . method PrintObjectOrientedDemo() println( me.ObjectOrientedField ) . public function ConsoleDo() // Note because we don't have an instantiated object yet, we need to create one to call its methods // which makes this function the only procedural one in this class methods script // demo as NodeRef of Class ObjectOrientedDemo = CreateNodeFromClass("ObjectOrientedDemo") demo.SetObjectOrientedDemo() demo.PrintObjectOrientedDemo() // Destroy the node since we do not need it anymore destroyNode( demo ) .
Call your code from the Console
- See also: Invoking scripts
Public functions may be called directly from the console's command line.
- Open up your console window by invoking HeroBlade's Panels Menu, choosing the Logging panel.
- Calling my class methods script produces the output seen in the window.
Exercise
Go forth and use what you have learned.
Video Tutorials
My First HSL Script, a brief, step-by-step tutorial.
HeroScript for Programmers pt 1
HeroScript for Programmers pt 2
HeroScript for Programmers pt 3
See also
Video: