SFS2X Docs / DevelopmentBasics / sfsobject-sfsarray
» SFSObject and SFSArray
SmartFoxServer 2X introduced two fundamental classes, SFSObject and SFSArray, that are central in the manipulation and transmission of data between the client and the server. The two classes are common across all API in all languages (server-side API included) making it very easy to port code to any platform and on every side of the application.
SFSObject and SFSArray represent a platform-neutral, high level objects that abstract the data transport between client and server. They are used to respectively represent data in form of a Map/Dictionary or List/Array; they can be nested to create complex data structures and they support many different data types (from bytes to integers, doubles, strings and a lot more).
These two classes provide fine-grained control over each data element sent over the network and offer high speed serialization using the default SFS2X binary protocol.
Let's consider this simple example: we need to send the data relative to a combat vehicle in a multiplayer game.
ISFSObject sfso = SFSObject.NewInstance(); sfso.PutByte("id", 10); sfso.PutShort("health", 5000); sfso.PutIntArray("pos", new Int[] {120, 150}); sfso.PutUtfString("name", "Hurricane");
var sfso = new SFS2X.SFSObject(); sfso.putByte("id", 10); sfso.putShort("health", 5000); sfso.putIntArray("pos", [120,150]); sfso.putUtfString("name", "Hurricane");
var sfso:ISFSObject = new FSObject(); sfso.putByte("id", 10); sfso.putShort("health", 5000); sfso.putIntArray("pos", [120,150]); sfso.putUtfString("name", "Hurricane");
In the code we use a single Byte (signed 8-bit) to send any small integer value, a Short (signed 16-bit) for larger values and integers for any number that should be represented as regular (signed) 32-bit value. In this example we use an integer array to send X,Y coordinates. Depending on the size of the game area, these coordinate values could be expressed with different types such as float, short.
» Supported data types
This is a list of all types supported by both classes:
Type | Bytes used | Ranges and Limits |
NULL | 1 | N/A |
BOOL | 1 | true/false |
BYTE | 1 | 8 bit Int, 0 - 2^8 (Java uses a signed byte) |
SHORT | 2 | 16 bit Int, -2^15 to 2^15 |
INT | 4 | 32 bit Int, -2^31 to 2^31 |
LONG | 8 | 64 bit Int, -2^63 to 2^63 |
FLOAT | 4 | See single precision floating point format |
DOUBLE | 8 | See double precision floating point format |
UTF-STRING |
variable | UTF-8 multibyte encoding max string len: 2^15 chars (32 KB) |
BOOL ARRAY | variable | max array len: 2^15 items (32767) |
BYTE ARRAY | variable | max array len: 2^31 items (2147483648) |
SHORT ARRAY | variable | max array len: 2^15 items (32767) |
INT ARRAY | variable | max array len: 2^15 items (32767) |
LONG ARRAY | variable | max array len: 2^15 items (32767) |
FLOAT ARRAY | variable | max array len: 2^15 items (32767) |
DOUBLE ARRAY | variable | max array len: 2^15 items (32767) |
UTF-STRING ARRAY | variable | max array len: 2^15 items (32767) |
SFSOBJECT | variable | max key-pair values: 2^15 items (32767) |
SFSARRAY | variable | max array len: 2^15 items (32767) |
CLASS | variable | N/A — Please check this advanced tutorial |
Array types are particularly useful when transmitting lists of values that are all of the same type, as the result is a very compact data structure. On the other hand, if you need to send a list of values with different types, the SFSObject will be the best choice.
NOTE for JavaScript and ActionScript 3 developers
There is a fundamental difference between JS/AS3 arrays and these arrays. The latter are dense arrays, meaning they must have a value (or at least null) in each index whereas JS/AS3 arrays don't have the same restriction.
» Example of usage
An example of SFSObject/SFSArray usage is in Extension development (see the Extensions Development sections for in-depth information), where they are employed to transmit every request and response. By expanding the example provided at the beginning of this article, let's see a full use case.
The client needs to transmit the following data to a server Extension (sfs is the SmartFox class instance):
void sendSomeData() { // Prepare data to be sent to the server Extension ISFSObject sfso = SFSObject.NewInstance(); sfso.PutByte("id", 10); sfso.PutShort("health", 5000); sfso.PutIntArray("pos", new Int[] {120, 150}); sfso.PutUtfString("name", "Hurricane"); // Send request to Zone level Extension on server side sfs.Send(new Sfs2X.Requests.ExtensionRequest("data", sfso)); }
function sendSomeData() { // Prepare data to be sent to the server Extension var sfso = new SFS2X.SFSObject(); sfso.putByte("id", 10); sfso.putShort("health", 5000); sfso.putIntArray("pos", [120,150]); sfso.putUtfString("name", "Hurricane"); // Send request to Zone level Extension on server side sfs.send(new SFS2X.ExtensionRequest("data", sfso)); }
public function sendSomeData():void { // Prepare data to be sent to the server Extension var sfso:ISFSObject = new FSObject(); sfso.putByte("id", 10); sfso.putShort("health", 5000); sfso.putIntArray("pos", [120,150]); sfso.putUtfString("name", "Hurricane"); // Send request to Zone level Extension on server side sfs.send(new ExtensionRequest("data", sfso)); }
The server Extension will receive the same data as the parameters object in one of its request handlers:
public class DataRequestHandler extends BaseClientRequestHandler { @Override public void handleClientRequest(User sender, ISFSObject params) { // Get the client parameters byte id = params.getByte("id"); short health = params.getShort("health"); Collection<Integer> pos = params.getIntArray("pos"); String name = params.getUtfString("name"); // Do something cool with the data ... } }
» Inspecting an SFSObject/SFSArray
Let's take a closer look at these two classes and see in detail what happens behind the scenes. Both objects provide two useful methods to dump their content in a hierarchical or hex-dump format.
If we take the same example provided at the beginning of the article and call the SFSObject.getDump method on the SFSObject we will see this output:
(short) health: 5000 (utf_string) name: Hurricane (byte) id: 10 (int_array) pos: [120, 150]
Each element in the object is listed with the format: (type) key-name: value
If you want a more low level view on how the object is represented in binary you can call the SFSObject.getHexDump method:
Binary size: 54 12 00 04 00 06 68 65 61 6C 74 68 03 13 88 00 04 .....health..... 6E 61 6D 65 08 00 09 48 75 72 72 69 63 61 6E 65 name...Hurricane 00 02 69 64 02 0A 00 03 70 6F 73 0C 00 02 00 00 ..id....pos..... 00 78 00 00 00 96
» Byte Arrays
One special mention should go to the ByteArray type which provides a mean of transferring binary data to and from the server. This could be used to transfer small files, images, media files, encrypted data, etc. For example Flash developers could improve the security of their application by sending external SWF files via the socket which would go undetected when spying the HTTP traffic.
When transferring large chunks of data we highly recommend to pre-compress them in order to optimize the size and avoid heavy strain on the Server side. In particular the dynamic protocol compression won't kick in for large bulks of data (tens/hundreds MB) in order to avoid significant performance degradation (especially under heavy concurrency). Zipping or gzipping the data before the transmission is highly recommended.
» SFSObject/SFSArray best practices
SFSObject and SFSArray are not thread safe, therefore additional care should be used when sharing these objects in a multi-threaded environment. In 90% of the cases SFSObject/SFSArray are only used for data transport and handled as local variables, so no concurrency is involved.
If you use several classes to represent the model in your games it is advisable to add toSFSObject and newFromSFSObject methods to your classes in ordert o help converting them to an SFSObject representation and viceversa. At least you should do it for those classes that are transferred over the network more often.
This is an example in Java where we suppose we have an RTS game with several vehicles that are updated often to the clients. Specifically we have a CombatQuad class representing one of the vehicles in the game. On the server side we will probably need at least the toSFSObject method to extract the relevant properties from the instance and send them in the update.
public class CombatQuad { private int unitID; private int posx; private int posy; private int energyLevel; private int bulletCount; public CombatQuad(int unitID) { this.unitID = unitID; this.energyLevel = 100; this.bulletCount = 20; } // ...More getters and setters... public ISFSObject toSFSObject() { ISFSObject sfso = new SFSObject(); sfso.putByte("id", unitID); sfso.putShort("px", posx); sfso.putShort("py", posy); sfso.putByte("el", energyLevel); sfso.putShort("bc", bulletCount); return sfso; } }
We omitted all the getters/setters to go straight to the point and show how this works. The toSFSObject method takes care of extracting the properties we need and also format them using the smallest data type possible. Our Extension code will be greatly simplified when it is time to send these object data in an update:
public void sendMapUpdate(CombatQuad quad, OtherObject other, User recipient) { ISFSObject responseObj = new SFSObject(); responseObj.putSFSObject("quad", quad.toSFSObject()); responseObj.putSFSObject("other", other.toSFSObject()); // Send data to client send("quadUpdate", responseObj, recipient); }
On the client side we will use a similar, although reverse, approach: we will rebuild the class instance from the properties transmitted via the SFSObject. In order to do so we will implement a static constructor called newFromSFSObject.
public class CombatQuad { private int unitID; private int posx; private int posy; private int energyLevel; private int bulletCount; public static CombatQuad NewFromSFSObject(ISFSObject sfso) { CombatQuad combatQuad = new CombatQuad(sfso.GetByte("id")); combatQuad.posx = sfso.GetShort("px"); combatQuad.posy = sfso.GetShort("py"); combatQuad.energyLevel = sfso.GetByte("el"); combatQuad.bulletCount = sfso.GetByte("bc"); return combatQuad; } public CombatQuad(int unitID) { this.unitID = unitID; } // ...More getters and setters... }
class CombatQuad { constructor(unitID) { this.unitID = unitID; } static newFromSFSObject(sfso) { var combatQuad = new CombatQuad(sfso.getByte("id")); combatQuad.posx = sfso.getShort("px"); combatQuad.posy = sfso.getShort("py"); combatQuad.energyLevel = sfso.getByte("el"); combatQuad.bulletCount = sfso.getByte("bc"); return combatQuad; } // ...More getters and setters... }
public class CombatQuad { private var unitID:int; private var posx:int; private var posy:int; private var energyLevel:int; private var bulletCount:int; public static function newFromSFSObject(sfso:ISFSObject):CombatQuad { var combatQuad:CombatQuad = new CombatQuad(sfso.getByte("id")); combatQuad.posx = sfso.getShort("px"); combatQuad.posy = sfso.getShort("py"); combatQuad.energyLevel = sfso.getByte("el"); combatQuad.bulletCount = sfso.getByte("bc"); return combatQuad; } function CombatQuad(unitID:int):void { this.unitID = unitID; } // ...More getters and setters... }
NOTE: UTF-8/multi-byte strings are not supported in SFSObject key names. In other words you should restrict key names to standard ASCII characters. It is also recommended to keep key names very short to save bandwidth.
» AS3 only: Object/Array to SFSObject/SFSArray conversion
In ActionScript 3 the SFSObject and SFSArray classes provide direct conversion to and from their respective native types: Object and Array. This was introduced to help migration from previous code written for SmartFoxServer 1.x and to provide some extra convenience for developers that use dynamic types.
It is necessary to underline that the use of generic Objects instead of Classes for the data model is in general not recommended, because it won't take advantage of the type optimizations introduced in ActionScript 3. Properties attached dynamically to an Object are not strongly-typed and they cannot be optimized by the compiler.
Anyways this can be useful in several occasions and it can help accelerating the development. The additional methods are the following.
- SFSObject.newFromObject: static constructor, generates a new SFSObject instance from an Object. Supported types are: null, Boolean, Number, String, Object, Array. The operation supports nested objects.
- SFSObject.toObject: converts the SFSObject instance into an Object, including nested objects.
- SFSArray.newFromArray: static constructor, generates a new SFSArray instance from an Array. Supported types are: null, Boolean, Number, String, Object, Array. The operation supports nested objects.
- SFSArray.toArray: converts the SFSArray instance into an Array, including nested objects.
The downsides of using this approach is that you loose the ability to fine tune the numeric types: all numbers will be converted to either a 32-bit integer (non decimal values) or 64-bit double (decimal values).
» More resources
For more details and examples on how to use SFSObject and SFSArray we highly recommend to take a look at the examples provided with SFS2X and consult the documentation found in the API Documentation section of this website. Also please check the Class serialization advanced tutorial.