Skip to content
cumulusdev edited this page Jan 15, 2012 · 62 revisions

CumulusServer includes a powerfull script-engine (using LUA langage) to create your own server applications. It allows to extend Cumulus behavior to add your specific needs in a flexible scripting-way.

Create a server application

On start, Cumulus creates a www folder in its root directory if it doesn't exist already. In this space, you can create subfolders, each one of them describes one server application. When a client connects to CumulusServer, the URL path relates the application to use. For example, the address rtmfp://host:port/myApplication search its corresponding application in [CumulusServer folder]/www/myService folder. First time CumulusServer builds and executes the file main.lua presents in this folder. Then, on new comer, the application already built is executed, unless main.lua file has been modified since the last time. Indeed when you edit main.lua file, the corresponding application is rebuilt in a dynamic way, without having to restart the server.

rtmfp://host:port/                   ->     [CumulusServer folder]/www/main.lua (root application)
rtmfp://host:port/myApplication      ->     [CumulusServer folder]/www/myApplication/main.lua
rtmfp://host:port/Games/myGame       ->     [CumulusServer folder]/www/Games/myGame/main.lua

Note The root application is built and started on CumulusServer start, whereas other server applications are started on first client connection for this application.

Each application is identified by its path, which is exactly the path part of the RTMFP URL connection. In the example above, root application has an empty string for path, then the both others have respectively /myApplication and /Games/myGame for path values.

Here a very simple first server application:

	function onStart(path)
		print("Server application '"..path.."' started)
	end
	function onStop(path)
		print("Server application '"..path.."' stopped)
	end

Cummunication between server applications

One application can access (read or write) to a variable of one other application, and can call one function of one other application. But it goes much further than that, applications can be specialized, and inherit them, exactly like inheritance of classes.

Application inheritance

Principle is simple, for example the /Games/myGame application extends the /Games application, and so all functions and variables available in /Games/main.lua are available in /Games/myGame/main.lua.

	-- /Games script application
	test = "I am Games"
	-- /Games/myGame script application
	print(test) -- displays "I am Games"

You can overload an inherited variable or an inherited function, and even dynamically remove the overload if need in putting value to nil.

	-- /Games/myGame script application
	print(test)          -- displays "I am Games"
	test = "I am myGame" -- overloads test variable
	print(test)          -- displays "I am myGame"
	test = nil           -- remove overloading test variable
	print(test)          -- displays "I am Games"

On variable overloading (or function overloading), you can always access for the parent version in prefixing with the parent application name.

	-- /Games/myGame script application
	print(test)          -- displays "I am Games"
	test = "I am myGame" -- overloads test variable
	print(test)          -- displays "I am myGame"
	print(Games.test)    -- displays "I am Games"

Note The root server application has for path an empty string, and is reached by the name www.

	-- '/' script application (the root server application)
	function hello()
		print("I am the root application")
	end
	-- /Games script application
	function hello()
		print("I am /Games application")
	end
	hello() -- displays "I am /Games application"
	www.hello() -- displays "I am the root application"

Exchange between unrelated server applications

In class inheritance, parent class has no knowledge of its children. However, here a parent server application can access for an child variable or function in checking before its existence. For example if /Games application would like to call a load function in /Games/myGame application, it have to check myGame existence, if myGame returns nil, it means that myGame doesn't exist or is not yet started.

	-- /Games script application
	if myGame then myGame.load() end
	-- /Games/myGame script application
	function load() end
		...
	end

By the same way, any applications can do the same thing with any other applications, even without hierarchical relationship.

	-- /myApplication script application
	if Games then
		if Games.myGame then Games.myGame.load() end
	end
	-- /Games/myGame script application
	function load() end
		...
	end

Cummunication between server and client

Pull data, Remote Call Procedure

You can call a server function from client, in giving multiple parameters, and function can return one result.

	function test(client,...)
		name,firstname = unpack(arg)
		return "Hello "..firstname.." "..name
	end
	_netConnection.client = this
	_netConnection.call("test",new Responder(onResult,onStatus),"Client","Test")

	function close():void { _netConnection.close() }
	function onStatus(status:Object):void {
		trace(status.description)
	}
	function onResult(response:Object):void {
		trace(response) // displays "Hello Test Client"
	}

Warning When you change default client of NetConnection, the new client must have a close() method which closes the connection, because a RTMFP Server can call this function in some special cases

Note that returned result of the scripting function is a writing shortcut for:

	function test(client,...)
		name,firstname = unpack(arg)
		client.writer.writeAMFResult("Hello "..firstname.." "..name)
	end

Both make exactly the same thing. If the function is not available, it returns a NetConnection.Call.Failed status event with Method 'test' not found in description. But you can also customize your own error event:

	function test(client,...)
		name,firstname = unpack(arg)
		if not firstname then error("test function takes two arguments") end
		return "Hello "..firstname.." "..name
	end
	_netConnection.client = this
	_netConnection.call("test",new Responder(onResult,onStatus),"Client");

	function close():void { _netConnection.close() }
	function onStatus(status:Object):void {
		trace(status.description) // displays "..main.lua:3: test function takes two arguments"
	}
	function onResult(response:Object):void {
		trace(response)
	}

Push data

Push data mechanism is also possible in using client.writer object.

	function pushData(client)
		client.writer.writeAMFMessage("onPushData","Application","Test")
	end
	function onPushData(name:String,firstName:String):void {
	}

Here an example of push data every two seconds (see Events part of Server Application, API page for onManage event description):

	writers = {}
	function onConnection(client,...)
		writers[client] = client.writer
	end
	function onDisconnection(client)
		writers[client] = nil
	end
	function onManage()
		for client,writer in pairs(writers) do
			writer.writeAMFMessage("refresh")
		end
	end
	function refresh():void {...}

client.writer returns the main flowWriter of this client. A FlowWriter is an unidirectional communication pipe, which allows to write message in a fifo for the client. Each flowWriter has some statistic exchange informations (qualityOfService, see Objects part of Server Application, API page for more details). When you want push a constant flow with a large amount of data, or if you want to get independant exchange statistics without disrupt the main flowWriter of one client, you can create your own flowWriter channel to push data:

	writers = {}
	function onConnection(client,...)
		writers[client] = client.writer.newFlowWriter()
	end
	function onDisconnection(client)
		writers[client] = nil
	end
	function onManage()
		for client,writer in pairs(writers) do
			writer.writeAMFMessage("refresh")
		end
	end
	function refresh():void {...}

When you create your own flowWriter, you can overload its onManage function, allowing you to write the same thing in a more elegant way, which avoid here writers table usage, and make the code really more short (see Objects part of Server Application, API page for more details).

	function onConnection(client,...)
		writer = client.writer.newFlowWriter()
		function writer:onManage()
			self.writeAMFMessage("refresh")
		end
	end
	function refresh():void {...}

AMF and LUA types corresponding

A conversion between AMF and LUA types allows to read AMF data from client, and write AMF data to client. Primitive conversion types are easy and intuitive (Number, Boolean, String). But concerning complex types like Array and Object, things go as following:

	-- LUA Table, map usage                   // AMF Object 
	{x=10,y=10,width=100,height=100}          {x:10,y:10,width:100,height:100}          

	-- LUA Table, array usage                 // AMF Array
	{10,10,100,100}                           new Array(10,10,100,100)

	-- LUA Table, mixed usage                // AMF Array associative
	{x=10,y=10,100,100}                      var mixed:Array = new Array(10,10,100,100);
						 mixed["x"] = 10;  mixed["y"] = 10;

	-- LUA Table, dictionary usage           // AMF Dictionary
	{10="test","test"=10,__size=2}          var dic:Dictionary = new Dictionary();
						 dic[10] = "test";  mixed["test"] = 10;

	-- LUA Table, byteArray usage            // AMF ByteArray
	{__raw="rawdata"}                        var data:ByteArray = new ByteArray();
						 data.writeUTFBytes("rawdata");

	-- LUA Table, date format                // AMF Date
	{year=1998,month=9,day=16,yday=259,      new Date(905989690435)
	wday=4,hour=23,min=48,sec=10,msec=435,
	isdst=false,__time=905989690435}       

Include/Require, LUA extension

LOGS

Clone this wiki locally