Skip to content

Commit

Permalink
blockdiagram, usc: add support for in-file merging of subsystems
Browse files Browse the repository at this point in the history
Signed-off-by: Markus Klotzbuecher <mk@mkio.de>
  • Loading branch information
kmarkus committed Jul 22, 2020
1 parent 9a7fa34 commit 129d804
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 33 deletions.
11 changes: 11 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ This file tracks user visible API changes

## 0.9.1

- `blockdiagram`, hierarchical composition: add support for adding
subsystems *without a namespace*, i.e. effectively merging them. If
a subsystem is added to the `subsystems` table *without* a name,
then it will me merged similar to when multiple models are provided
on the cmdline. However, in the latter case, the merged models
override existing entries, whereas when specified in `subsystems`,
the parent subsystem takes precedence. This feature is mainly useful
to avoid deep hierarchies. In addition this cleans up the merge
function and adds a verbose `-v` option to `ubx-launch`, for
printing early messages (mainly merge actions for now).

- `ubx-mq`
- add support for writing to mqueues. Example:
`ubx-mq write mymq "{0,0.01,0,0.1,0}" -r 0.1`
Expand Down
26 changes: 26 additions & 0 deletions docs/user/composing_systems.rst
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,28 @@ to a non-hierarchical one, however:
of multiple identically named node configs, the one at the highest
level will be selected.

.. _merging-subsystems:

Merging subsystems
~~~~~~~~~~~~~~~~~~

It is possible to add a subsystem without a namespace, as shown by the
following snippet:

.. code:: lua
return bd.system {
subsystems = {
bd.load("subsys1.usc"),
}
}
In this case, the ``subsys1.usc`` system will be merged directly into
the parent system. Note that entries of the parent system take
precedence, so in case of conflicts elements of the subsystem will be
skipped.

This feature is useful to avoid an extra hierarchy level.

Model mixins
------------
Expand All @@ -260,6 +282,10 @@ For example, consider the example in
ubx-launch -webif -c deep_composition.usc,ptrig.usc
**Note**: unlike merging from within the usc using an unnamed
``subsystems`` entry (see :ref:`merging-subsystems`), models merged on
the command line will *override* existing entries.

Alternatives
------------

Expand Down
165 changes: 134 additions & 31 deletions lua/blockdiagram.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ local strict = require "strict"

local M={}

-- module config
M.LOG_STDERR = false
M.VERBOSE = false

-- shortcuts
local foreach = utils.foreach
local tab2str = utils.tab2str
local ts = tostring
local fmt = string.format
local insert = table.insert
local safets = ubx.safe_tostr

-- node configuration
_NC = nil
USE_STDERR = false
local _NC = nil

local red=ubx.red
local blue=ubx.blue
Expand All @@ -34,14 +38,26 @@ local green=ubx.green
local yellow=ubx.yellow
local magenta=ubx.magenta

local crit, err, warn, notice, info = nil, nil, nil, nil, nil
local function undef_log()
utils.stderr("logger undefined, call def_loggers before using")
os.exit(1)
end

local crit, err, warn, notice, info =
undef_log, undef_log, undef_log, undef_log, undef_log

local function stderr(msg)
if USE_STDERR then utils.stderr(msg) end
-- log function verbose messages
local function log(format, ...)
if M.VERBOSE then utils.stderr(fmt(format, unpack{...})) end
end

--- Create logging helper functions.
--- Create logging helper functions
-- These log to ubx-log and depending on LOG_STDERR to stderr
local function def_loggers(nd, src)
local function stderr(msg)
if M.LOG_STDERR then utils.stderr(msg) end
end

err = function(format, ...)
local msg = fmt(format, unpack{...}); stderr(msg); ubx.err(nd, src, msg)
end
Expand Down Expand Up @@ -456,6 +472,12 @@ end

--- System constructor
function system:init()
-- merge the subsystems without a namespace, i.e. those that are
-- in the array part of the table. weak merge, override = false.
for i,s in ipairs(self.subsystems or {}) do
system.merge(self, s, false)
self.subsystems[i] = nil
end
system_populate_meta(self)
end

Expand Down Expand Up @@ -916,40 +938,121 @@ local function connect_blocks(nd, root_sys)
end

--- Merge one system into another
-- No duplicate handling. Running the validation after a merge is strongly recommended.
--
-- The override flag allows to control how result will behave in case
-- of conflicts of blocks configurations. If true, then the merged content
-- will take precedence, if false not.
--
-- @param self targed of the merge
-- @param sys system which to merge into self
function system.merge(self, sys)
local function do_merge(dest, src)
foreach(function (imp) insert(dest.imports, imp) end, src.imports)
foreach(function (blk) insert(dest.blocks, blk) end, src.blocks)
foreach(function (cfg) insert(dest.configurations, cfg) end, src.configurations)
foreach(function (conn) insert(dest.connections, conn) end, src.connections)

foreach(
function (ndcfg, name)
if dest.node_configurations[name] then
warn("merge: overriding existing destination system node_config %s", name)
-- @param override if true (default) then merge so
-- @param verbose log merge operations
function system.merge(self, sys, override)
if override == nil then override = true end

local function merge_block(x)
for i,b in ipairs(self.blocks) do
if b.name == x.name then
if override then
log("merge, block: replacing %s with %s", tab2str(b), tab2str(x))
self.blocks[i] = x
else
log("merge, block: skipping %s with %s", tab2str(b), tab2str(x))
end
dest.node_configurations[name] = ndcfg
end, src.node_configurations)
return
end
end
-- not match found, append it
log("merge, block: appending %s", tab2str(x))
insert(self.blocks, x)
end

if src.subsystems and not dest.subsystems then dest.subsystems={} end
local function merge_config(x)
for _,c in ipairs(self.configurations) do

-- if it exists, merge it config by config
if c.name == x.name then
local dest = c.config
local src = x.config

for k,v in pairs(src) do
if dest[k] then
if override then
log("merge, config: replacing %s.%s with %s", c.name, k, tab2str(src[k]))
dest[k] = src[k]
else
log("merge, config: skipping %s.%s with %s", c.name, k, tab2str(src[k]))
end
else
log("merge, config: adding %s.%s with %s", c.name, k, tab2str(src[k]))
dest[k] = src[k]
end
end
return
end
end
log("merge, config: appending %s", tab2str(x))
insert(self.configurations, x)
end

foreach(
function (subsys, name)
if dest.subsystems[name] then
warn("merge: overriding existing destination subsystem %s", name)
local function merge_conn(x)
for i,c in ipairs(self.connections) do
if c.src == x.src and c.tgt == x.tgt then
if override then
log("merge, conn: replacing %s with %s", tab2str(c), tab2str(x))
self.connections[i] = x
else
log("merge, conn: skipping %s with %s", tab2str(c), tab2str(x))
end
dest.subsystems[name] = subsys
end, src.subsystems)
return
end
end
log("merge, conn: appending %s", tab2str(x))
insert(self.connections, x)
end

local function merge_ndcfg(x, name)
if self.node_configurations[name] then
if override then
log("merge, ndcfg: replacing nodecfg %s (%s) with %s",
name, tab2str(self.node_configurations[name]), tab2str(x))
self.node_configurations[name] = x
else
log("merge, ndcfg: skipping override of nodecfg %s (%s) with %s",
name, tab2str(self.node_configurations[name]), tab2str(x))
end
return
end
self.node_configurations[name] = x
end

if src.subsystems then
do_merge(dest.subsystems, src.subsystems)
local function merge_subsys(x, name)
if self.subsystems[name] then
if override then
log("merge, subsys: replacing subsys %s", name)
self.subsystems[name] = x
else
log("merge, subsys: skipping override of subsys %s", name)
end
return
end
self.subsystems[name] = x
end

do_merge(self, sys)
if sys.node_configurations and not self.node_configurations then
self.node_configurations = {}
end
if sys.subsystems and not self.subsystems then
self.subsystems={}
end

foreach(function (imp) insert(self.imports, imp) end, sys.imports)
foreach(merge_block, sys.blocks)
foreach(merge_config, sys.configurations)
foreach(merge_conn, sys.connections)
foreach(merge_ndcfg, sys.node_configurations)
foreach(merge_subsys, sys.subsystems)

end

--- Launch a blockdiagram system
Expand All @@ -963,7 +1066,7 @@ function system.launch(self, t)
-- fire it up
t = t or {}
t.nodename = t.nodename or "n"
USE_STDERR = t.use_stderr or false
M.LOG_STDERR = t.use_stderr or false

local nd = ubx.node_create(t.nodename,
{ loglevel=t.loglevel,
Expand Down
7 changes: 5 additions & 2 deletions tools/ubx-launch
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ usage: ubx-launch [OPTION] -c <model file>
-t SECONDS run for SECONDS and then shutdown
-loglevel N set global loglevel [0..7]
-s log to stderr (in addition to ubx_log)
-v verbose (will log additional, early messages to stderr)
-webif [PORT] create and start webinterface block.
the optional port defaults to 8888.
-validate don't run, just validate configuration file
-validate don't run, just validate configuration file
-check CHK1[,CHK2] carry out additional validation
available checks:
Expand Down Expand Up @@ -77,6 +78,8 @@ else
conf_files = utils.split(opttab['-c'][1], ',')
end

if opttab['-v'] then bd.VERBOSE=true end

if opttab['-ctype'] then
if not opttab['-ctype'][1] then
print("error: -ctype option requires a type argument")
Expand Down Expand Up @@ -106,7 +109,7 @@ end
for i=2,#conf_files do
local m = bd.load(conf_files[i], conf_types[i])
print("merging "..conf_files[i].. " into ".. conf_files[1])
model:merge(m)
model:merge(m, true)
end

if opttab['-nodename'] then
Expand Down

0 comments on commit 129d804

Please sign in to comment.