Skip to content

Commit 34fcf16

Browse files
committed
all: Add DataBroker and Signals concepts
TODO: - Add documentation on how to use it. - Add tests using them. Author: Martin Henselmeyer <[email protected]>
1 parent c60a3ef commit 34fcf16

27 files changed

+4277
-16
lines changed

engine/lua/cloe-engine/init.lua

+9
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ local engine = {
3434

3535
--- @type number Number of triggers processed from the initial input.
3636
triggers_processed = 0,
37+
38+
--- @type table<string, string> Map of signal names to regular expression matches.
39+
signal_aliases = {},
40+
41+
--- @type string[] List of signals to make available during simulation.
42+
signal_requires = {},
3743
},
3844

3945
--- Contains engine state for a simulation.
@@ -80,6 +86,9 @@ local engine = {
8086

8187
--- @type table<string, table> Namespaced Lua interfaces of instantiated plugins.
8288
plugins = {},
89+
90+
--- @type table<string, userdata> Table of required signals.
91+
signals = {},
8392
}
8493

8594
require("cloe-engine.types")

engine/lua/cloe/engine.lua

+152
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,158 @@ function engine.log(level, fmt, ...)
124124
api.log(level, "lua", msg)
125125
end
126126

127+
--- Alias a set of signals in the Cloe data broker.
128+
---
129+
--- @param list table<string, string> # regular expression to alias key
130+
--- @return table<string, string> # current signal aliases table
131+
function engine.alias_signals(list)
132+
-- TODO: Throw an error if simulation already started.
133+
api.initial_input.signal_aliases = luax.tbl_extend("force", api.initial_input.signal_aliases, list)
134+
return api.initial_input.signal_aliases
135+
end
136+
137+
--- Require a set of signals to be made available via the Cloe data broker.
138+
---
139+
--- @param list string[] signals to merge into main list of required signals
140+
--- @return string[] # merged list of signals
141+
function engine.require_signals(list)
142+
-- TODO: Throw an error if simulation already started.
143+
api.initial_input.signal_requires = luax.tbl_extend("force", api.initial_input.signal_requires, list)
144+
return api.initial_input.signal_requires
145+
end
146+
147+
--- Optionally alias and require a set of signals from a signals enum list.
148+
---
149+
--- This allows you to make an enum somewhere which the language server
150+
--- can use for autocompletion and which you can use as an alias:
151+
---
152+
--- ---@enum Sig
153+
--- local Sig = {
154+
--- DriverDoorLatch = "vehicle::framework::chassis::.*driver_door::latch",
155+
--- VehicleMps = "vehicle::sensors::chassis::velocity",
156+
--- }
157+
--- cloe.require_signals_enum(Sig, true)
158+
---
159+
--- Later, you can use the enum with `cloe.signal()`:
160+
---
161+
--- cloe.signal(Sig.DriverDoorLatch)
162+
---
163+
--- @param enum table<string, string> input mappging from enum name to signal name
164+
--- @param alias boolean whether to treat signal names as alias regular expressions
165+
--- @return nil
166+
function engine.require_signals_enum(enum, alias)
167+
-- TODO: Throw an error if simulation already started.
168+
local signals = {}
169+
if alias then
170+
local aliases = {}
171+
for key, sigregex in pairs(enum) do
172+
table.insert(aliases, { sigregex, key })
173+
table.insert(signals, key)
174+
end
175+
engine.alias_signals(aliases)
176+
else
177+
for _, signame in pairs(enum) do
178+
table.insert(signals, signame)
179+
end
180+
end
181+
engine.require_signals(signals)
182+
end
183+
184+
--- Return full list of loaded signals.
185+
---
186+
--- Example:
187+
---
188+
--- local signals = cloe.signals()
189+
--- signals[SigName] = value
190+
---
191+
--- @return table
192+
function engine.signals()
193+
return api.signals
194+
end
195+
196+
--- Return the specified signal.
197+
---
198+
--- If the signal does not exist, nil is returned.
199+
---
200+
--- If you want to set the signal, you need to use `cloe.set_signal()`
201+
--- or access the value via `cloe.signals()`.
202+
---
203+
--- @param name string signal name
204+
--- @return any|nil # signal value
205+
function engine.signal(name)
206+
return api.signals[name]
207+
end
208+
209+
--- Set the specified signal with a value.
210+
---
211+
--- @param name string signal name
212+
--- @param value any signal value
213+
--- @return nil
214+
function engine.set_signal(name, value)
215+
api.signals[name] = value
216+
end
217+
218+
--- Record the given list of signals into the report.
219+
---
220+
--- This can be called multiple times, but if the signal is already
221+
--- being recorded, then an error will be raised.
222+
---
223+
--- This should be called before simulation starts,
224+
--- so not from a scheduled callback.
225+
---
226+
--- You can pass it a list of signals to record, or a mapping
227+
--- from name to
228+
---
229+
--- @param mapping table<number|string, string|fun():any> mapping from signal names
230+
--- @return nil
231+
function engine.record_signals(mapping)
232+
validate("cloe.record_signals(table)", mapping)
233+
api.state.report.signals = api.state.report.signals or {}
234+
local signals = api.state.report.signals
235+
signals.time = signals.time or {}
236+
for sig, getter in pairs(mapping) do
237+
if type(sig) == "number" then
238+
if type(getter) ~= "string" then
239+
error("positional signals can only be signal names")
240+
end
241+
sig = getter
242+
end
243+
if signals[sig] then
244+
error("signal already exists: " .. sig)
245+
end
246+
signals[sig] = {}
247+
end
248+
249+
engine.schedule({
250+
on = "loop",
251+
pin = true,
252+
run = function(sync)
253+
local last_time = signals.time[#signals.time]
254+
local cur_time = sync:time():ms()
255+
if last_time ~= cur_time then
256+
table.insert(signals.time, cur_time)
257+
end
258+
259+
for name, getter in pairs(mapping) do
260+
local value
261+
if type(name) == "number" then
262+
name = getter
263+
end
264+
if type(getter) == "string" then
265+
value = engine.signal(getter)
266+
else
267+
value = getter()
268+
end
269+
if value == nil then
270+
-- TODO: Improve error message!
271+
error("nil value received as signal value")
272+
end
273+
table.insert(signals[name], value)
274+
end
275+
end,
276+
})
277+
end
278+
127279
--- Schedule a trigger.
128280
---
129281
--- It is not recommended to use this low-level function, as it is viable to change.

engine/src/coordinator.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ void to_json(Json& j, const HistoryTrigger& t) {
5656
j["at"] = t.when;
5757
}
5858

59-
Coordinator::Coordinator(sol::state_view lua)
60-
: lua_(lua), executer_registrar_(trigger_registrar(Source::TRIGGER)) {}
59+
Coordinator::Coordinator(sol::state_view lua, cloe::DataBroker* db)
60+
: lua_(lua), executer_registrar_(trigger_registrar(Source::TRIGGER)), db_(db) {}
6161

6262
class TriggerRegistrar : public cloe::TriggerRegistrar {
6363
public:

engine/src/coordinator.hpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ struct HistoryTrigger {
9898
*/
9999
class Coordinator {
100100
public:
101-
Coordinator(sol::state_view lua);
101+
Coordinator(sol::state_view lua, cloe::DataBroker* db);
102102

103103
const std::vector<HistoryTrigger>& history() const { return history_; }
104104

@@ -109,6 +109,8 @@ class Coordinator {
109109

110110
sol::table register_lua_table(const std::string& field);
111111

112+
cloe::DataBroker* data_broker() const { return db_; }
113+
112114
std::shared_ptr<cloe::TriggerRegistrar> trigger_registrar(cloe::Source s);
113115

114116
void enroll(cloe::Registrar& r);
@@ -149,6 +151,7 @@ class Coordinator {
149151
std::map<std::string, cloe::ActionFactoryPtr> actions_;
150152
std::map<std::string, cloe::EventFactoryPtr> events_;
151153
sol::state_view lua_;
154+
cloe::DataBroker* db_; // non-owning
152155

153156
// Execution:
154157
std::shared_ptr<cloe::TriggerRegistrar> executer_registrar_;

engine/src/registrar.hpp

+9-3
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ namespace engine {
3333

3434
class Registrar : public cloe::Registrar {
3535
public:
36-
Registrar(std::unique_ptr<ServerRegistrar> r, Coordinator* c)
37-
: server_registrar_(std::move(r)), coordinator_(c) {}
36+
Registrar(std::unique_ptr<ServerRegistrar> r, Coordinator* c, cloe::DataBroker* db)
37+
: server_registrar_(std::move(r)), coordinator_(c), data_broker_(db) {}
3838

3939
Registrar(const Registrar& ar,
4040
const std::string& trigger_prefix,
4141
const std::string& static_prefix,
4242
const std::string& api_prefix)
43-
: coordinator_(ar.coordinator_) {
43+
: coordinator_(ar.coordinator_), data_broker_(ar.data_broker_) {
4444
if (trigger_prefix.empty()) {
4545
trigger_prefix_ = ar.trigger_prefix_;
4646
} else {
@@ -107,9 +107,15 @@ class Registrar : public cloe::Registrar {
107107
return coordinator_->register_lua_table(trigger_prefix_);
108108
}
109109

110+
cloe::DataBroker& data_broker() const override {
111+
assert(data_broker_ != nullptr);
112+
return *data_broker_;
113+
}
114+
110115
private:
111116
std::unique_ptr<ServerRegistrar> server_registrar_;
112117
Coordinator* coordinator_; // non-owning
118+
cloe::DataBroker* data_broker_; // non-owning
113119
std::string trigger_prefix_;
114120
};
115121

0 commit comments

Comments
 (0)