You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
10 KiB
273 lines
10 KiB
oc-basecontrol
|
|
==============
|
|
|
|
`oc-basecontrol` is a library for distributed base control systems in
|
|
OpenComputers.
|
|
|
|
|
|
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
|
|
local bc = require("bc"):new()
|
|
bc:register("foo_noun", 1234)
|
|
bc:finalize()
|
|
```
|
|
|
|
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 acumulate 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)
|
|
```
|
|
|
|
### `BaseControl:set(name, value)`
|
|
Set a noun or verb to a new value. This only works if `name` is local.
|
|
You can not set remote nouns. If `name` is a verb, value **must** be a function
|
|
which will be the new callback.
|
|
```lua
|
|
local bc = require("bc"):new()
|
|
bc:register("some_noun", 1234)
|
|
bc:finalize()
|
|
|
|
-- Later ...
|
|
bc:set("some_noun", 4321)
|
|
```
|
|
|
|
### `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. `get` will return `nil` if a name is not known.
|
|
```lua
|
|
local bc = require("bc"):finalize{"some_noun"}
|
|
print("Value: ", bc:get("some_noun"))
|
|
```
|
|
|
|
### `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.
|
|
`call` will return `true` if an attempt was made to run the verb and `false`
|
|
otherwise. `call` specifically does **not** error, because no guarantees
|
|
could be made anyway.
|
|
|
|
_Note_: For local verbs, the call will still be synchroneous.
|
|
```lua
|
|
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")
|
|
```
|
|
|
|
### `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 error upon
|
|
the timeout expiring.
|
|
|
|
_Note_: For local verbs, the timeout can not be adhered to.
|
|
```lua
|
|
local bc = require("bc")
|
|
bc:register("stupid_add", function(a, b)
|
|
return a + b
|
|
end)
|
|
bc:finalize()
|
|
|
|
-- This call can either happen locally or on another node
|
|
local res = bc:call_sync("stupid_add", nil, 12, 34)
|
|
print("12 + 34 = "..res)
|
|
```
|
|
|
|
### `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. `listen` returns a unique id for the installed listener
|
|
that could later be used to cancel it. `listen` also returns a boolean as the
|
|
second value: It indicates whether an attempt was made at installing the
|
|
listener. If the noun was not yet known (`false`), the listener is still kept
|
|
and will be installed as soon as the noun is registered.
|
|
```lua
|
|
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)
|
|
```
|
|
|
|
### `BaseControl:cancel(noun, id)`
|
|
Cancel a previously installed listener. The id will be invalid after this call.
|
|
```lua
|
|
local listenid = bc:listen("some_noun", bc.Query.Change, function(value)
|
|
print("'some_noun' is now '"..value.."'")
|
|
end)
|
|
|
|
-- Later ...
|
|
bc:cancel("some_noun", listenid)
|
|
```
|
|
|
|
### `BaseControl:close()`
|
|
End this base-controller and notify others of the local nouns and verbs going
|
|
offline. Close will also cancel all installed listeners.
|
|
|
|
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([local_only])`
|
|
Returns a list of all known nouns (either local or remote). If `local_only` is
|
|
boolean true, only a list of local nouns will be returned.
|
|
|
|
### `BaseControl:verbs([local_only])`
|
|
Returns a list of all known verbs (either local or remote). If `local_only` is
|
|
boolean true, only a list of local verbs will be returned.
|
|
|
|
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:pull(filter_func, timeout)`
|
|
Block until a message arrives for which `filter_func` returns `true`. The signature
|
|
of `filter_func` is `function(addr, msg_tbl)`, similar to the callback given to
|
|
`Network:start`. If `timeout` is not `nil`, `pull` should return after the
|
|
timeout expired as well.
|
|
|
|
**Important**: When pulling on an event, the message handler given to `start`
|
|
**must** have run before pull returns! Make sure your implementation upholds
|
|
this guarantee!
|
|
|
|
### `Network:stop()`
|
|
Close this connection and uninstall the message handler.
|