From 132cfb9921d417b81e490191ccbaf5cfbff86266 Mon Sep 17 00:00:00 2001 From: Rahix Date: Sun, 14 Apr 2019 19:12:56 +0200 Subject: [PATCH] Update README Signed-off-by: Rahix --- README.md | 246 +++++++++++++++++++++++++++++++++++++++++++++------- scratch.lua | 21 ----- 2 files changed, 213 insertions(+), 54 deletions(-) delete mode 100644 scratch.lua diff --git a/README.md b/README.md index ebee6ae0f386..12725bf5473a 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,237 @@ oc-basecontrol ============== -oc-basecontrol is an OpenComputers generic base control library. +`oc-basecontrol` is a library for distributed base control systems in +OpenComputers. -## Documentation ## -Load oc-basecontrol into your application: +Concept +------- +Each node in the base network exports a set of _nouns_ and _verbs_. + +- _Nouns_ are values which can be queried by other nodes and which + can change over time. Other nodes can also request to be notified + about updates of nouns. This is called _listening_. Examples: The power + a capacitor has stored or the fluid level in a tank. +- _Verbs_ are functions that can be triggered remotely. There are two + ways this can be done: + - _Asynchroneously_, which is "fire-and-forget". There is no checking that + the call succeeded and no way to pass back a return value. + - _Synchroneous_ verb calling, where the caller will block until a response + with a return value is returned or the call timed out. +- _Listening_: Nodes can request a notification on a noun change event. + There are a few different queries that can be used here: + - `Query.Change`: Notify whenever the noun changes + - `Query.Rising`: Notify whenever the noun's value increases + - `Query.Falling`: Notify whenever the noun's value decreases + - `Query.Equals(v)`: Notify whenever the noun's value equals a given value + - `Query.Above(v)`: Notify whenever the noun's value changes and is above + a given limit + - `Query.Below(v)`: Notify whenever the noun's value changes and is below + a given limit + + +Basic API +--------- + +### `BaseControl:new([modem])` +There are a few different ways to construct the base-controller. The easiest +is to just use the object created during loading the library: ```lua -bc = require("bc"):init(, ) +local bc = require("bc"):new() +bc:register("foo_noun", 1234) +bc:finalize() ``` -where `` is a table containing all nouns (described later) and their -initial value and `` is a table containing all verbs and their callbacks. -The basic idea behind basecontrol is, that network nodes have a set of **nouns**, -which represent different values. A computer near a reactor for example could have -a noun called `power-output`. Network nodes also have **verbs**, actions that other -nodes can perform. The reactor computer could have a `shutdown` verb for example. +If you need finer grained control, you can also use the initial object as a +class to create base-controllers: +```lua +local BaseControl = require("bc") +local bc = BaseControl() +-- or +local bc = BaseControl:new() +``` +The main job of the constructor is to initialize the network interface. +This means as soon as the constructor is called, our node will go "online". + +You can give a custom network to `new` if you do not want to use the default +one. More info under _Network Configuration_. + +### `BaseControl:register(name, value)` +Registers a new noun or verb named `name` with the initial value of `value`. +If `value` is a _function_, it will be a verb. `register` will fail if `name` +is already known as a local noun or verb. Note that locals always take +precedence over remotes so `register` will happily add a local which a remote +also exports. +```lua +local bc = require("bc") +-- Register a noun +bc:register("some_noun", 12345) +-- Register a verb +bc:register("some_verb", function(param_a, param_b) + print("Verb called with "..param_a.." and "..param_b..".") +end) +bc:finalize() +``` +You can also batch-register multiple nouns/verbs: +```lua +local bc = require("bc") +bc:register{ + some_noun=1234, + other_noun=4321, + a_verb=function(param) + print("Param: ", param) + end, +} +``` + +### `BaseControl:finalize([waits], [timeout])` +After creating a new base-controller, it will not immediately announce its data +into the network. It will first acuumulate local nouns and verbs and then send +them out in one batch. This happens when you call `finalize`. + +Finalize takes two optional parameters: +- `waits`: A list of remote nouns/verbs that this node requires to be available. + `finalize` will only return once all of them have been registered by remote + nodes. +- `timeout`: Return early if timeout was reached before all waits were found. + +If finalize is called again, it will start waiting once more, but it will not +reannounce its own presence. `finalize` can also be used as a constructor, +like this: +```lua +local bc = require("bc"):finalize{"some_noun"} +-- With timeout +local bc = require("bc"):finalize({"some_noun"}, 10) +``` -The `bc` object has the following methods to interface with verbs and nouns: +### `BaseControl:set(name, value)` +Set a noun to a new value. This only works if the noun `name` is a local one. +You can not set remote nouns. If `name` is a verb, value **must** be a function +which will be the new callback. ```lua --- Retrieve a noun from it's network node -val = bc:get_noun("") +local bc = require("bc"):new() +bc:register("some_noun", 1234) +bc:finalize() --- Set a noun on the local machine -bc:set_noun("", value) +-- Later ... +bc:set("some_noun", 4321) +``` --- Call a verb on it's network node -bc:call_verb("", param) +### `BaseControl:get(noun, [timeout])` +Get the value of a noun named `noun`. `get` can not be called for verbs. +If `timeout` is supplied, the call will return early if no response came before +the timeout expired. +```lua +local bc = require("bc"):finalize{"some_noun"} +print("Value: ", bc:get("some_noun")) ``` -Additionally, there is +### `BaseControl:call(verb, ...)` +Call a verb **asynchroneously**. All parameters following `verb` will be given +to the remote function. There is no guarantee that the verb was actually run. + +_Note_: For local verbs, the call will still be synchroneous. ```lua -id = bc:listen_noun("", event_type, event_arg, function callback) +local bc = require("bc") +bc:register("some_verb", function(param_a, param_b) + print("Verb called with "..param_a.." and "..param_b..".") +end) +bc:finalize() + +-- This call can either happen locally or on another node +bc:call("some_verb", "param_a_value", "param_b_value") ``` -which is used to register asynchroneous callbacks on (possibly remote) noun -changes. `event_type` is one of +### `BaseControl:call_sync(verb, timeout, ...)` +Call a verb **synchroneously**. All parameters following `verb` will be given +to the remote function. `call_sync` will return the remote function's return +value. If timeout is not `nil` or `0`, `call_sync` will return early upon +the timeout expiring. +```lua +local bc = require("bc") +bc:register("stupid_add", function(a, b) + return a + b +end) +bc:finalize() -* `onchange` => whenever the value changes, `event_arg` is ignored -* `onrising` => whenever the value gets bigger, `event_arg` is ignored -* `onfalling` => whenever the value gets smaller, `event_arg` is ignored -* `onvalue` => whenever the value equals `event_arg` -* `onabove` => whenever the value is bigger than `event_arg` -* `onbelow` => whenever the value smaller than `event_arg` +-- This call can either happen locally or on another node +local res = bc:call_sync("stupid_add", nil, 12, 34) +print("12 + 34 = "..res) +``` -A listener can be removed using +### `BaseControl:listen(noun, query, callback)` +Install a listener on changes of remote nouns. `noun` must be a remote noun. +If it is not known at this time, the listener is still installed in hope that +the node will come online later. `query` must be a valid query, chosen from +`BaseControl.Query`. A full list of currently supported queries was shown above. +The `callback` function will get one argument: The new value of the noun that +triggered the event. ```lua -bc:listen_cancel("", id) +bc = require("bc"):finalize{"some_noun"} +bc:listen("some_noun", bc.Query.Change, function(value) + print("'some_noun' is now '"..value.."'") +end) +bc:listen("some_noun", bc.Query.Below(0), function(value) + print("'some_noun' is negative! ("..value..")") +end) ``` -## Testing ## -Testing is done using a "fake" backend and typing -```console -$ ./lunit test_bc.lua +Introspection API +----------------- +The following methods give you insight into the network: + +### `BaseControl:has_noun(noun)` +Whether `noun` is known at this time (either local or remote). Note that even if +a noun is known, it might still not be available if the remote node exited +without deregistering. + +### `BaseControl:has_verb(verb)` +Whether `verb` is known at this time (either local or remote). Note that even if +a verb is known, calling it might still not succeed if the remote node exited +without deregistering. If you need to know for sure, call using `call_sync`. + +### `BaseControl:nouns()` +Returns a list of all known nouns (either local or remote). + +### `BaseControl:verbs()` +Returns a list of all known verbs (either local or remote). + +Network Configuration +--------------------- +_oc-basecontrol_ uses the raw [Modem API](https://ocdoc.cil.li/component:modem) with the default modem +(`component.modem`), if no alternative network is supplied to `BaseControl:new`. +If you have multiple modems and want to base-control to use a specific one, +you can set it like this: +```lua +local BaseControl = require("bc") +local bc = BaseControl:new(BaseControl.Network:new(my_moden)) ``` +The Network constructor looks like this: `function Network:new([modem], [port])`. +You can leave out the modem (it will use the default) and just specify a port, +if you just don't like the default port. If you leave out the port or give no +custom network at all, _oc-basecontrol_ will use a port that is derived from the +version number. This prevents different versions of _oc-basecontrol_ clashing. + +Alternatively, you can use a completely different networking stack by writing +your own implementation of `Network`. Take a look at the sources to see how +the default implementation works. A custom network needs to have the following +methods: + +### `Network:start(callback)` +Start this network connection (eg. open the port) and install `callback` as a +listener for incoming messages. `callback` has the following signature: +`function callback(remote_addr, msg_tbl)` where `remote_addr` is the address of +the other side (any string works here) and `msg_tbl` is the unserialized message. +For serialization, please use `OpenOS`'s `serialization` library. + +### `Network:send(addr, msg_tbl)` +Send a message to the remote party identified by `addr`. `msg_tbl` should be +serialized using `OpenOS`'s `serialization` library. + +### `Network:broadcast(msg_tbl)` +Send a message to all connected nodes on the network. + +### `Network:stop()` +Close this connection and uninstall the message handler. diff --git a/scratch.lua b/scratch.lua deleted file mode 100644 index 91348fdaee4c..000000000000 --- a/scratch.lua +++ /dev/null @@ -1,21 +0,0 @@ -inspect = require("inspect").inspect -bc = require("bc") -network = require("network") - - -bc1 = bc:init({["light"]=true}, {["toggle_light"]=function(b) - b:set_noun("light", not b:get_noun("light")) -end}) -a1 = network.get_scene() -bc2 = bc:init({}, {}) -a2 = network.get_scene() - -network.set_scene(a1) -print(true, bc1:call_verb("toggle_light")) -print(false, bc1:get_noun("light")) - -network.set_scene(a2) -print(true, bc2:call_verb("toggle_light")) - -network.set_scene(a1) -print(true, bc1:get_noun("light"))