1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 08:39:30 +00:00
anselme/doc/server.md

18 KiB

Instead of the scripts running in the same Lua process as the one of your game, Anselme can run in a Client-Server mode. This allows:

  • Anselme to run in a separate thread and therefore not affect your game's frame times (Anselme is not very fast)
  • to use Anselme other game engine that don't use Lua

The server is the process that holds and process the Anselme state. Typically, the server would run in a separate process or thread that your game.

The client connects to a server and sends instructions to execute Anselmes scripts and receive the response. Typically, the client correspond to your game.

For now, the whole system assumes that there is a single client per server - so you should not share a single server among serveral client.

How the Client and Server communicate between each other is defined using a RPC object. Out-of-the-box, Anselme provides RPC objects that can communicate over LÖVE threads, and over JSON-RPC 2.0; these can be easily created using the functions in anselme.server. If you want to implement a custom RPC mechanism, you can look at the existing implementations in anselme/server/rpc/.

Example usage in a LÖVE game:

local server = require("anselme.server")

-- create a new client+server
local client = server.new_love_thread()
client:load_stdlib()

-- load an anselme script file in a new branch
local run_state = client:branch("block")
run_state:run_file("script.ans")

-- start script
run_state:step(handle_event)

-- callback to handle an anselme event
function handle_event(event, data)
	if event == "text" then
		show_dialog_box {
			lines = data,
			on_dialog_box_closed = function()
				run_state:step(handle_event) -- resume script
			end
		}
	elseif event == "choice" then
		show_choice_dialog_box {
			choices = data,
			on_dialog_box_closed = function(choice_number)
				run_state:choose(choice_number)
				run_state:step(handle_event)
			end
		}
	elseif event == "return" then
		run_state:merge()
		run_state:remove() -- remove branch from server
	elseif event == "error" then
		print("error in anselme thread!", data)
		run_state:remove()
	end
end

function love.update()
	client:process() -- handle messages coming from the server
end

anselme.server

Main functions to create clients and servers.

.new_love_thread ()

Starts a Server in a new LÖVE thread and returns a Client connected to that server.

Should be called from a LÖVE game code only.

defined at line 10 of anselme/server/init.lua: new_love_thread = function()

.new_json_rpc_server (send, receive)

Returns a new Server that communicate with a Client using JSON-RPC 2.0.

This does not define how the two comminicate (through sockets, http, etc.), you will need to define this using the send and receive arguments.

send(message) is a function that send a single message to the associated Client.

receive(block) is a function that receive a single message from the associated Client (or nil if no message available). If block is true, the function is allowed to block execution until a message is received.

defined at line 41 of anselme/server/init.lua: new_json_rpc_server = function(send, receive)

.new_json_rpc_client (send, receive)

Returns a new Client that communicate with a Server using JSON-RPC 2.0.

This does not define how the two comminicate (through sockets, http, etc.), you will need to define this using the send and receive arguments.

send(message) is a function that send a single message to the associated Server.

receive(block) is a function that receive a single message from the associated Server (or nil if no message available). If block is true, the function is allowed to block execution until a message is received.

defined at line 53 of anselme/server/init.lua: new_json_rpc_client = function(send, receive)

Client

This is a Lua implementation of an Anselme client, with a nice API that mirrors the Anselme State API to communicate with the server.

Usage: create a Client object using the functions in the anselme.server module and call server:process() regularly to process messages from the Server.

The API available here tries to follow the State API as much as possible, with the following differences:

  • functions that return a value in State take an additionnal argument callback:
    • if it is a function callback(ret1, ret2, ...), it is called as soon as the return values ret1, ret2, ... are received. The function also returns the identifier call_id associated with the callback (to optionally cancel the callback later using client:cancel(call_id)).
    • if it is nil, return values are discarded;
    • if it is the string "block", the call will block until the return values are received. The function returns these values directly.
  • functions that returns a State in State now returns a Client;
  • return values are converted to a simpler representation if possible (no metamethods, userdata or cycles) to make serialization simpler - in particular, Anselme values are automatically converted to Lua primitives.
  • a few new methods are introduced, see below.

Implementing a Client in other languages should be relatively easy: if your client language has a JSON-RPC 2.0 library, point it to the Anselme server you started using server.new_json_rpc_server() and you're done. You should then be able to call any of the methods described in the Server. Additionnaly, if you plan to use the define_rpc or define_local_rpc server methods, you will need to implement the following remote method in your client that will be called by the server:

  • call(function_id, ...) where function_id (string) is the function identifier that was given when define_rpc or define_local_rpc was called, and ... is a list of arguments. This must call the function associated with the function_id using the given arguments, and returns the values returned by the call (as a list of return values: {ret1, ret2, ...}).

:process (block)

Process received messages.

Must be called regularly. If block is true, the function is allowed to block execution until a message is received.

defined at line 51 of anselme/server/Client.lua: process = function(self, block)

:cancel (call_id)

Cancel the callback associated with the call call_id. This does not stop the remote method execution; only prevent the callback from being called.

defined at line 57 of anselme/server/Client.lua: cancel = function(self, call_id)

:choose (i)

If the last event was a choice, choose the i-th choice. This must be called before calling :step again after receiving a choice event.

defined at line 63 of anselme/server/Client.lua: choose = function(self, i)

:remove ()

Remove the branch from the server. The branch (and therefore this Client branch) can't be used after calling this method.

defined at line 68 of anselme/server/Client.lua: remove = function(self)

:define_rpc (name, args, func)

Defines a function in the global scope, that calls the Lua function func on the Client when called.

The function will not be sent to the server; it will be directly executed on the client (i.e. your game code) each time a script on the server needs it to be called.

Usage: client:define_rpc("teleport", "(position)", function(position) player:teleport(position) end)

defined at line 78 of anselme/server/Client.lua: define_rpc = function(self, name, args, func)

:define_local_rpc (name, args, func)

Same as :define_rpc, but define the function in the current scope.

defined at line 85 of anselme/server/Client.lua: define_local_rpc = function(self, name, args, func)

Methods and fields that mirror the State API

:load_stdlib (language)

Same as state:load_stdlib(language).

defined at line 95 of anselme/server/Client.lua: load_stdlib = function(self, language)

.branch_id

Same as state.branch_id.

defined at line 100 of anselme/server/Client.lua: branch_id = "main",

.source_branch

Same as state.source_branch, but refers to the source Client instead of a State.

defined at line 102 of anselme/server/Client.lua: source_branch = nil,

:branch (branch_id, callback)

Same as state:branch(branch_id), but returns a new Client instead of a State.

defined at line 104 of anselme/server/Client.lua: branch = function(self, branch_id, callback)

:merge ()

Same as state:merge().

defined at line 113 of anselme/server/Client.lua: merge = function(self)

:define (name, value, func_code, raw_mode)

Same as state:define(name, value, func, raw_mode), but if func_code is given, it must be a string containing the function code.

Note that the given code will be executed on the server, and that there is no sandboxing of any kind;

Example: client:define("main", "print", "(message::is string)", "function(message) print(message) end").

defined at line 122 of anselme/server/Client.lua: define = function(self, name, value, func_code, raw_mode)

:define_local (name, value, func_code, raw_mode)

Same as define, but calls state:define_local(name, value, func, raw_mode).

defined at line 126 of anselme/server/Client.lua: define_local = function(self, name, value, func_code, raw_mode)

:defined (name, callback)

Same as state:defined(name).

defined at line 130 of anselme/server/Client.lua: defined = function(self, name, callback)

:defined_local (name, callback)

Same as state:defined_local(name).

defined at line 134 of anselme/server/Client.lua: defined_local = function(self, name, callback)

:save (callback)

Same as state:save().

defined at line 139 of anselme/server/Client.lua: save = function(self, callback)

:load (save)

Same as state:load(save).

defined at line 143 of anselme/server/Client.lua: load = function(self, save)

:active (callback)

Same as state:active().

defined at line 148 of anselme/server/Client.lua: active = function(self, callback)

:state (callback)

Same as state:state().

defined at line 152 of anselme/server/Client.lua: state = function(self, callback)

:run (code, source, tags)

Same as state:run(code, source, tags).

defined at line 156 of anselme/server/Client.lua: run = function(self, code, source, tags)

:run_file (path, tags)

Same as state:run_file(code, source, tags).

defined at line 160 of anselme/server/Client.lua: run_file = function(self, path, tags)

:step (callback)

Same as state:step), but returns:

  • for text and choice events, a list of lines { { { text = "line 1 part 2", tags = { ... } }, ... }, ... } (in other word, TextEventData and ChoiceEventData stripped of everything but their list of text parts);
  • for return events, the return value converted to Lua primitives;
  • for other events, it will try to return the event data as-is.

defined at line 167 of anselme/server/Client.lua: step = function(self, callback)

:interrupt (code, source, tags)

Same as state:interrupt(code, source, tags).

defined at line 171 of anselme/server/Client.lua: interrupt = function(self, code, source, tags)

:eval (code, source, tags, callback)

Same as state:eval(code, source, tags), but the returned value is converted to Lua primitives.

defined at line 175 of anselme/server/Client.lua: eval = function(self, code, source, tags, callback)

:eval_local (code, source, tags, callback)

Same as state:eval_local(code, source, tags), but the returned value is converted to Lua primitives.

defined at line 179 of anselme/server/Client.lua: eval_local = function(self, code, source, tags, callback)

Server

An Anselme server instance.

Usage: create a Server object using the functions in the anselme.server module and call server:process() regularly to process messages from the Client.

If you are implementing your own client, the following methods are available to be remotely called by your client:

  • Note:
    • in all the following methods, the first parameter branch_id (string) is the id of the Anselme branch to operate on;
    • methods that return something always returns a list of return values: { ret1, ret2, ... }.
  • choose(branch_id, i): if the last event was a choice, choose the i-th (number) line in the choice list;
  • remove(branch_id): removes the branch from the server; no further operation will be possible on the branch;
  • load_stdlib(branch_id, language): calls state:load_stdlib(language) on the branch;
  • branch(branch_id[, new_branch_id]): calls state:branch(branch_id) on the branch; returns the id of the new branch (string);
  • merge(branch_id): calls state:merge() on the branch;
  • define(branch_id, name, args, func_code, raw_mode): calls state:define(branch_id, name, args, func, raw_mode) on the branch; if func_code is given, func will be a function generated from the Lua code func_code (string, example: define("main", "print", "(message::is string)", "function(message) print(message) end")). Note that whatever is in func_code will be executed on the server, and that there is no sandboxing of any kind;
  • define_rpc(branch_id, name, args, func_id): defines a function in the branch that, when called, will call the remote method call(func_id, ...) on the client and block until it returns. In other words, this allows the Anselme script running on the server to transparently call the function that is associated with the id func_id on the client.
  • define_local(branch_id, name, args, func_code, raw_mode): same as define, but calls state:define_local(branch_id, name, args, func, raw_mode);
  • define_local_rpc(branch_id, name, args, func_id): same as define_rpc, but defines the function in the current scope;
  • defined(branch_id, name): calls state:defined(name) on the branch and returns its result;
  • defined_local(branch_id, name): calls state:defined_local(name) on the branch and returns its result;
  • save(branch_id): calls state:save() on the branch and returns its result;
  • load(branch_id, save): calls state:load(save) on the branch;
  • active(branch_id): calls state:active() on the branch and returns its result;
  • state(branch_id): calls state:state() on the branch and returns its result;
  • run(branch_id, code, source, tags): calls state:run(code, source, tags) on the branch;
  • run_file(branch_id, path, tags): calls state:run_file(path, tags) on the branch;
  • step(branch_id): calls state:step() on the branch and returns:
    • for text and choices events, a list of lines { { { text = "line 1 part 2", tags = { ... } }, ... }, ... } (in other word, TextEventData and ChoiceEventData stripped of everything but their list of text parts);
    • for return events, the return value converted to Lua;
    • for other events, it will try to return the event data as-is.
  • interrupt(branch_id, code, source, tags): calls state:interrupt(code, source, tags) on the branch;
  • eval(branch_id, code, source, tags): calls state:eval(code, source, tags) on the branch and returns its result, converted to Lua;
  • eval_local(branch_id, code, source, tags): calls state:eval_local(code, source, tags) on the branch and returns its result, converted to Lua.

:process (block)

Process received messages.

Must be called regularly. If block is true, the function is allowed to block execution until a message is received.

defined at line 160 of anselme/server/Server.lua: process = function(self, block)


file generated at 2024-11-17T15:00:50Z