Close

Improvements - 20190130a

A project log for The Internet of Nixie Clocks

Wherein the tubes of Nix do bridge betwix the worlds of form and ether. I see no reason one should bridge, but that won't stop me, either.

ziggurat29ziggurat29 02/03/2019 at 19:430 Comments

Summary

Improve the 'call sequencer' such that we can schedule calls even if a call sequence is currently in-progress.

Deets

The call sequencer that was produced for the benefit of controlling the Nixie clock has a deficiency:  you mustn't schedule a call sequence when a call sequence is already in-progress.  This would result in interleaved calls, and also consumes more timer resources.

This currently isn't a problem since the call sequencer is only used for updating the Nixie clock once per day, so there is no likelihood of two sequences coming in at once, however I do want the mechanism to be well-behaved later, when I add a server component to issue arbitrary sequences at arbitrary times.

Presently, the sequencer takes a sequence (a Lua table) and keeps an index into where we are in the sequence, and a periodic timer is used to issue the call and increment the index.  When we have finished, we destroy the timer.  Through the magic of closures, the timer servicing function captures the index and the call sequence, so nothing needs to be done special there to keep them alive or destroy them when done.

In this new design, we will instead have a separate sequence object, and we will append to it sequences that are requested.  If there is not existing sequence object being serviced, we will create a new one and kick start the process.  Otherwise, the process is pretty much the same.

function run_sequence ( sequence, period_ms )
    if ( not sequence or 0 == #sequence ) then return end --silly cases

    -- if we have a run sequence in process, merely append these items to it
    if ( _tblRunSequence ) then
        table_append ( _tblRunSequence, sequence )
        return
    end

    -- otherwise, kick off the sequence

    -- do the first one immediately
    local nIdx = 1
    sequence[nIdx]()
    nIdx = nIdx + 1
    -- if that was it, beat it
    if ( nIdx > #sequence ) then return end

    --remaining ones are to be paced out
    _tblRunSequence = table_clone(sequence) -- we make this global
    local pacingTimer = tmr.create()
    pacingTimer:alarm(period_ms, tmr.ALARM_AUTO, function(timer)
        if ( nIdx <= #_tblRunSequence ) then
            _tblRunSequence[nIdx]()
            nIdx = nIdx + 1
        end
        if ( nIdx > #_tblRunSequence ) then
            -- we are completed
            timer:stop()
            timer:unregister()
            _tblRunSequence = nil
        end
    end)
end

In this implementation, we create a global object _runsequence which will contain the stuff being serviced.  This object is global, because we need to access it later in subsequent call invocations, and captures won't help us here.  If it exists, we append do it and we're done.  If it doesn't exist, then we clone our given sequence into it, and kick off the timer.  And if the timer finds that it has run out of things to do, it destroys itself, and the global sequence object.  So, if there is nothing going on, all memory should be released for garbage collection.

One quirk of Lua is that it intends to be a minimalist runtime system, and you are expected to add code to do things as needed, rather than having an especially rich standard runtime environment.  In this case, notably lacking are the ability to make a copy of a Lua table -- assignments are by reference -- and the ability to append a table onto another table.  So we have to write that code.

This method creates a table that is a deep-ish copy of a source table:

function table_clone(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[table_clone(orig_key)] = table_clone(orig_value)
        end
        setmetatable(copy, table_clone(getmetatable(orig)))
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end

And this method appends the values of one table's 'array' section onto another table:

function table_append ( t, o )
    for _, v in ipairs ( o ) do
        table.insert ( t, v )
    end
end

Testing

If you past the following into the Lua shell command prompt:

= _tblRunSequence
run_sequence ( 
{
function () print ( "Hey!" ) end,
function () print ( "Ho!" ) end,
}
, 5000 )
run_sequence ( 
{
function () print ( "Hi!" ) end,
function () print ( "Yo!" ) end,
}
, 5000 )
= _tblRunSequence

Then you will get the following output:

> = _tblRunSequence
nil
> run_sequence (
>> {
>> function () print ( "Hey!" ) end,
>> function () print ( "Ho!" ) end,
>> }
>> , 5000 )
Hey!
> run_sequence (
>> {
>> function () print ( "Hi!" ) end,
>> function () print ( "Yo!" ) end,
>> }
>> , 5000 )
> = _tblRunSequence
table: 0x3fff0c90
> Ho!
Hi!
Yo!
= _tblRunSequence
nil
>

This shows that at the start, there was no global _tblRunSequence.  We registered a call sequence that simply prints some distinctive text.  The first function was executed immediately because there was no sequence in-progress.  Then we see that the global _tblRunSequence has been created.  The sequence pacing is 5 sec, so we will have some time before it has completed.  Then we register a new call sequence.  We can sit back an observe that the calls have been made in the expected pacing.  When the sequence is complete, we can issue a final '= _tblRunSequence' and see that the global sequencing variable has been deleted.

Tada!  Since it's working, it's time to move this stuff to LFS.  I did a pass with dummy_strings.lua empty so I could regenerate that list, then I set the list to the current values, and restarted.  I did a =node.heap() before running init.lua, and one after, and I got 43704 - 39128 = 4576.  So, with the stuff in LFS, the program is nominally using less than 5 K RAM.

Now I should be set up to be able to safely implement some sort of 'server' to issue Nixie calls over the Internet.

Next

Some sort of server?

Discussions