<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<br>
<div class="moz-cite-prefix">Am 11.09.20 um 08:53 schrieb Mark
Webb-Johnson:<br>
</div>
<blockquote type="cite"
cite="mid:8CFF9592-1921-47C5-AA2E-1F76BF40D9F6@webb-johnson.net">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<div class="">
<blockquote type="cite" class="">
<div class="">There is no need to extend OvmsCommand. The
DuktapeObject is meant to be used as a general binding of
arbitrary system objects to Duktape, so the system objects
don't need to be extended.</div>
</blockquote>
<div class=""><br class="">
</div>
I agree it is not necessary; my suggestion is purely to clean it
up and enhance functionality. The problem at the moment is that
OvmsCommand execute callbacks can only be to function callbacks
(not objects). It doesn’t even use the c++ bind function
callback mechanism (like notification, etc, for example). It is
what it is, and changing now is very hard.</div>
</blockquote>
<br>
That slipped my attention, but upgrading OvmsCommand to accept any
function type should be simply changing m_execute and m_validate to
std::function, or do I miss something?<br>
<br>
<blockquote type="cite"
cite="mid:8CFF9592-1921-47C5-AA2E-1F76BF40D9F6@webb-johnson.net">
<div class="">However, making the execute function virtual is
relatively simple and allows the object to be virtualised.
Alternatively, we could provide an alternative proper c++
function callback variant.</div>
<div class=""><br class="">
</div>
<div class="">Without that, we need something kludgy
like DukOvmsCommandRegisterRun that has to try to find
the DuktapeConsoleCommand command in order to be able to call
the execute callback in duktape. The current implementation
needs to keep a map of OvmsCommand => DuktapeConsoleCommand
in order to do this (which is messy).</div>
</blockquote>
<br>
I think the map wouldn't be necessary for the command invocation,
but it would still be necessary to check/get the associated
DuktapeConsoleCommand of a parent OvmsCommand pointer.<br>
<br>
<blockquote type="cite"
cite="mid:8CFF9592-1921-47C5-AA2E-1F76BF40D9F6@webb-johnson.net">
<div class="">
<blockquote type="cite" class="">
<div class="">My API design would be like this:</div>
</blockquote>
<div class=""><br class="">
</div>
OK</div>
<div class=""><br class="">
</div>
<div class="">
<blockquote type="cite" class="">
<div class="">The forced unregistering limits registering
commands to library plugins (which don't have an unload
operation yet). I think this is an unnecessary limitation, I
would like to be able to register a command by adding a
registration call to "ovmsmain.js" or by executing a script.
Of course that means commands registered that way need to be
unregistered explicitly, but that's up to the script
developers & users then. I don't think we should limit
them here without good reason.<br class="">
<br class="">
On a Duktape unload/reload operation, all JS objects get
finalized anyway because of heap destruction, so deletion
would happen automatically by the DuktapeConsoleCommand
finalizer. If we add some module unload operation later on,
modules may e.g. define a cleanup callback, or we can add
the module filename to the DuktapeObject registry -- the
latter option would also automatically apply to all
DuktapeObject instances, eliminating the need for separate
registries for other system bindings potentially to be added
in the future.<br class="">
<br class="">
Implicit OvmsCommand deletions can happen as the order of
deletion is undefined. Inhibiting this should be a straight
forward use of the reference count though: on registration
of a sub command, simply Ref() all parent commands
registered by Duktape as well. Unref() them after the sub
command has been removed, the last Unref() on a parent then
automatically deletes it (the first Ref() is done by the
coupling).</div>
</blockquote>
<div class=""><br class="">
</div>
OK. My only concern would be timing. Say there is a script that
is run, registers and command, then exits. The command will be
cleaned up in the next duktape memory clean. But before then,
the command could be called. If everything is running in that
one duktape thread, there should be no crash, I think, but it
sounds scary to me.</div>
</blockquote>
<br>
I would keep the command registered until it's explicitly
unregistered or deleted by a heap destruction. I also suggest
keeping a Ref() on the DuktapeConsoleCommand until the command has
been fully deregistered…<br>
<br>
Hm. Deregistration can still occur concurrently to the OvmsCommand
deletion… I think we haven't taken care of concurrent command
deregistration at all, there is no lock/mutex in OvmsCommandApp. So
currently any subsystem deregistering it's commands can crash the
system if one of those commands is just about to be executed. Maybe
we need to address this in OvmsCommandApp or …Map.<br>
<br>
Regards,<br>
Michael<br>
<br>
<br>
<blockquote type="cite"
cite="mid:8CFF9592-1921-47C5-AA2E-1F76BF40D9F6@webb-johnson.net">
<div class=""><br class="">
</div>
<div class="">Overall, your suggested approach sounds ok.</div>
<div class=""><br class="">
</div>
<div class="">Regards, Mark.<br class="">
<div><br class="">
<blockquote type="cite" class="">
<div class="">On 8 Sep 2020, at 4:13 PM, Michael Balzer <<a
href="mailto:dexter@expeedo.de" class=""
moz-do-not-send="true">dexter@expeedo.de</a>> wrote:</div>
<br class="Apple-interchange-newline">
<div class="">
<meta http-equiv="Content-Type" content="text/html;
charset=UTF-8" class="">
<div class=""> Mark,<br class="">
<br class="">
for the synchronous callback execution, that's simply a
variant of OvmsDuktape::DuktapeRequestCallback() using
the already existing DuktapeDispatchWait() method along
with passing the OvmsWriter* in dmsg.writer. Could be
named "…ExecuteCallback" to reflect the synchronous
nature.<br class="">
<br class="">
There is no need to extend OvmsCommand. The
DuktapeObject is meant to be used as a general binding
of arbitrary system objects to Duktape, so the system
objects don't need to be extended.<br class="">
<br class="">
From my first check of your implementation, I would
probably:<br class="">
<ul class="">
<li class="">Redefine the register API to expect an
object with an "execute" (and later possibly also a
"validate") callback, and bind the
DuktapeCommandObject to that object. A "fail"
callback could also make sense.<br class="">
</li>
<li class="">Delegate the full OvmsCommand life cycle
and execution handling to the DuktapeCommandObject.</li>
<li class="">Remove the forced unregistering of
commands on script file unloading.<br class="">
</li>
<li class="">Inhibit implicit sub command deletions by
parent deletions.<br class="">
</li>
</ul>
My API design would be like this:<br class="">
<br class="">
<tt class="">// Command registration:<br class="">
var cmd = OvmsCommand.Register({</tt><tt class=""><br
class="">
</tt><tt class=""> parent: "test",</tt><tt class=""><br
class="">
</tt><tt class=""> name: "dukcommand",</tt><tt class=""><br
class="">
</tt><tt class=""> title: "Test/demonstrate Duktape
command registration",</tt><tt class=""><br class="">
</tt><tt class=""> usage: "Pass 1 - 3 arguments",</tt><tt
class=""><br class="">
</tt><tt class=""> min: 1,</tt><tt class=""><br
class="">
</tt><tt class=""> max: 3,</tt><tt class=""><br
class="">
</tt><tt class=""> execute: function(verbosity, argv)
{},</tt><tt class=""><br class="">
</tt><tt class=""> fail: function(error) {}</tt><tt
class=""><br class="">
</tt><tt class="">});<br class="">
<br class="">
</tt><tt class=""><tt class="">// Command
deregistration:<br class="">
</tt>OvmsCommand.Unregister({ parent: "test", name:
"dukcommand" });<br class="">
// …or in case we still have the cmd object, simply:<br
class="">
OvmsCommand.Unregister(cmd);<br class="">
<br class="">
</tt><br class="">
The OvmsCommand execution callback would be a bound
method of the DuktapeCommandObject instance. The method
passes the verbosity and arguments vector to
ExecuteMethod() via the data pointer using a custom
struct. CallMethod() then pushes these on the Duktape
stack and calls the "execute" property.<br class="">
<br class="">
The forced unregistering limits registering commands to
library plugins (which don't have an unload operation
yet). I think this is an unnecessary limitation, I would
like to be able to register a command by adding a
registration call to "ovmsmain.js" or by executing a
script. Of course that means commands registered that
way need to be unregistered explicitly, but that's up to
the script developers & users then. I don't think we
should limit them here without good reason.<br class="">
<br class="">
On a Duktape unload/reload operation, all JS objects get
finalized anyway because of heap destruction, so
deletion would happen automatically by the
DuktapeConsoleCommand finalizer. If we add some module
unload operation later on, modules may e.g. define a
cleanup callback, or we can add the module filename to
the DuktapeObject registry -- the latter option would
also automatically apply to all DuktapeObject instances,
eliminating the need for separate registries for other
system bindings potentially to be added in the future.<br
class="">
<br class="">
Implicit OvmsCommand deletions can happen as the order
of deletion is undefined. Inhibiting this should be a
straight forward use of the reference count though: on
registration of a sub command, simply Ref() all parent
commands registered by Duktape as well. Unref() them
after the sub command has been removed, the last Unref()
on a parent then automatically deletes it (the first
Ref() is done by the coupling).<br class="">
<br class="">
Regards,<br class="">
Michael<br class="">
<br class="">
<br class="">
<div class="moz-cite-prefix">Am 08.09.20 um 09:06
schrieb Mark Webb-Johnson:<br class="">
</div>
<blockquote type="cite"
cite="mid:0D6C31A8-544C-4FD6-A9C6-1500D3CE6F66@webb-johnson.net"
class="">
<meta http-equiv="Content-Type" content="text/html;
charset=UTF-8" class="">
Michael,
<div class=""><br class="">
</div>
<div class="">Very clear, and very helpful. Only thing
I would suggest would be to have a minimum example
in the documentation. The bare minimum required for
an implementation of an object.</div>
<div class=""><br class="">
</div>
<div class="">Reading through what you write, it seems
the correct approach is:</div>
<div class=""><br class="">
</div>
<div class="">
<ul class="MailOutline">
<li class="">Extend OvmsCommand to have a virtual
ExecuteCommand method (same parameters as
‘m_execute’ callback).</li>
<li class="">Extend OvmsCommand::execute to check
if m_execute is null, then call ‘ExecuteCommand’
instead.</li>
<li class="">Perhaps do the same for m_validate in
OvmsCommand.</li>
<li class="">Then, the duktape implementation can
be object (rather than callback function) based,
as your DuktapeObject expects. The javascript
would call a function to register a command,
with a command object to be used for the
callback.</li>
<li class="">Need to extend the DuktapeObject
system to support a synchronous command
(presumably implemented with a passed mutex like
we do in several other parts of the system).</li>
</ul>
</div>
<div class=""><br class="">
</div>
<div class="">Is that correct, and what you were
expecting? Or any other suggestions?</div>
<div class=""><br class="">
</div>
<div class="">Regards, Mark.</div>
<div class=""><br class="">
<div class="">
<blockquote type="cite" class="">
<div class="">On 7 Sep 2020, at 2:55 AM, Michael
Balzer <<a href="mailto:dexter@expeedo.de"
class="" moz-do-not-send="true">dexter@expeedo.de</a>>
wrote:</div>
<br class="Apple-interchange-newline">
<div class="">
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8" class="">
<div class=""> Mark,<br class="">
<br class="">
<blockquote type="cite" class="">
<div class="">Still struggling with this.
It seems like your DuktapeObject will do
this, but I can’t work out how it works.</div>
</blockquote>
<br class="">
I admit my documentation has some
shortcomings. I'll try to fill that gap
first:<br class="">
<br class="">
<br class="">
<b class=""><u class="">Concept #1:
Reference counting</u></b><br class="">
<br class="">
A DuktapeObject is meant to be a C++/OS
extension of a Javascript object, for
example to hold some system binding
associated with the JS object.<br class="">
<br class="">
The primary goal is to provide asynchronous
operations on the system side, initiating JS
callbacks when finishing/failing. For
example a HTTP operation can be started by a
script, passing some "done" callback. The
system then starts the network operation
asynchronously, the JS task can continue
processing other scripts. Once the network
operation is done, the DuktapeObject sends a
request to the Duktape task to execute the
"done" callback on itself.<br class="">
<br class="">
So the DuktapeObject is normally shared by
multiple contexts and parallel operations.
Asynchronous operation also means the JS
object or context may already be gone when
the operation finishes. So the DuktapeObject
needs to stay locked in memory independent
of the JS context.<br class="">
<br class="">
That's implemented by counting the active
references to the DuktapeObject (methods
Ref() / Unref()). The last Unref()
automatically deletes the DuktapeObject
instance.<br class="">
<br class="">
For example: JS requests a network
operation. The initial JS binding (see
coupling) sets the reference count to 1. The
DuktapeObject starts the network request,
increasing the ref count to 2. If the JS
context now dies (decreasing the reference
count), the DuktapeObject will still remain
valid until the network operation returns.<br
class="">
<br class="">
As the Ref/Unref operations need a
(recursive) mutex, that's also part of the
DuktapeObject and exposed by the API for
other uses: Lock() / Unlock().<br class="">
<br class="">
<br class="">
<b class=""><u class="">Concept #2: Coupling</u></b><br
class="">
<br class="">
Javascript does not have a destructor
concept. JS objects get deleted by heap
destruction or by the garbage collector when
no reference to the JS object is left. In
both cases, the actual object deletion is
called "finalization", and a special
finalizer method/callback can be installed
on a JS object to be called just before the
object gets deallocated. That is done by the
DuktapeObject::Couple() method (implicitly
called when constructed directly with a JS
object reference).<br class="">
<br class="">
There is no way to force finalization on a
JS object. So a DuktapeObject cannot tell
Duktape to delete it's coupled object, that
means a DuktapeObject should normally not be
deleted from outside the Duktape context, at
least not if still coupled to the JS object.
Coupling and decoupling can only be done in
the Duktape context.<br class="">
<br class="">
The standard finalizer
DuktapeObject::Finalizer() simply decouples,
automatically deleting itself if the
coupling was the last reference. This is a
virtual method, so can be overridden as
necessary.<br class="">
<br class="">
The coupling operation additionally adds a
hidden pointer to the DuktapeObject instance
in the JS object. That allows to check for
and retreive associated DuktapeObject
instances from any JS object, which is
provided by the GetInstance() call.<br
class="">
<br class="">
<br class="">
<b class=""><u class="">Concept #3:
Registration</u></b><br class="">
<br class="">
For asynchronous operations, it's normally
very convenient to have a "fire &
forget" API. Example from the documentation:<br
class="">
<pre id="codecell13" style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4; color: rgb(64, 64, 64); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" class=""><span class="nx" style="box-sizing: border-box;">VFS</span><span class="p" style="box-sizing: border-box;">.</span><span class="nx" style="box-sizing: border-box;">Save</span><span class="p" style="box-sizing: border-box;">({</span>
<span class="nx" style="box-sizing: border-box;">path</span><span class="o" style="box-sizing: border-box; color: rgb(102, 102, 102);">:</span> <span class="s2" style="box-sizing: border-box; color: rgb(186, 33, 33);">"/sd/mydata/telemetry.json"</span><span class="p" style="box-sizing: border-box;">,</span>
<span class="nx" style="box-sizing: border-box;">data</span><span class="o" style="box-sizing: border-box; color: rgb(102, 102, 102);">:</span> <span class="nx" style="box-sizing: border-box;">Duktape</span><span class="p" style="box-sizing: border-box;">.</span><span class="nx" style="box-sizing: border-box;">enc</span><span class="p" style="box-sizing: border-box;">(</span><span class="s1" style="box-sizing: border-box; color: rgb(186, 33, 33);">'jx'</span><span class="p" style="box-sizing: border-box;">,</span> <span class="nx" style="box-sizing: border-box;">telemetry</span><span class="p" style="box-sizing: border-box;">),</span>
<span class="nx" style="box-sizing: border-box;">fail</span><span class="o" style="box-sizing: border-box; color: rgb(102, 102, 102);">:</span> <span class="kd" style="box-sizing: border-box; color: rgb(0, 128, 0); font-weight: bold;">function</span><span class="p" style="box-sizing: border-box;">(</span><span class="nx" style="box-sizing: border-box;">error</span><span class="p" style="box-sizing: border-box;">)</span> <span class="p" style="box-sizing: border-box;">{</span>
<span class="nx" style="box-sizing: border-box;">print</span><span class="p" style="box-sizing: border-box;">(</span><span class="s2" style="box-sizing: border-box; color: rgb(186, 33, 33);">"Error saving telemetry: "</span> <span class="o" style="box-sizing: border-box; color: rgb(102, 102, 102);">+</span> <span class="nx" style="box-sizing: border-box;">error</span><span class="p" style="box-sizing: border-box;">);</span>
<span class="p" style="box-sizing: border-box;">}</span>
<span class="p" style="box-sizing: border-box;">});</span>
</pre>
I.e. you simply pass the operation arguments
including the done/fail callbacks to an API
method and don't need to care about storing
a reference to some handle. In JS that
normally means the object used won't have
any reference left after the call, so would
be deleted by the garbage collector on the
next run.<br class="">
<br class="">
To avoid garbage collection and lock the JS
object in memory, we need to store a
reference to it in a "public" place. Duktape
provides a special public place for this,
hidden from scripts, called the global
stash. DuktapeObject maintains a dedicated
global object registry in that stash.<br
class="">
<br class="">
Adding and removing the coupled object
reference to/from that registry is done by
the Register() and Deregister() methods.<br
class="">
<br class="">
So for asynchronous system operations, or
system integrations that shall be
persistent, you normally do a Register()
call together with the coupling, unless some
ressource isn't available. Deregistration is
then normally done when all pending JS
callbacks have been executed, or when the
persistent system integration has been
unbound.<br class="">
<br class="">
Other API designs are possible here: if
you'd rather like the script needing to
store a reference to your operation handle,
you don't need to do a registration. The
object will then be deleted (finalized) by
the garbage collector automatically after
the script deletes the reference.<br
class="">
<br class="">
<br class="">
<b class=""><u class="">Concept #4: Callback
invocation</u></b><br class="">
<br class="">
Triggers on the system side, for example a
finished or failed network operation, shall
normally trigger a JS method execution.<br
class="">
<br class="">
JS callback methods are simply passed as
part of the arguments object in modern JS
APIs. This allows to pass simple function
definitions inline, as well as to reference
a separately defined general handler
function. JS allows functions to be excuted
in the context of any object, and callbacks
normally are executed in the context of the
API object. This adds even more convenience,
as the callbacks can easily access the other
API arguments still stored in the object, as
well as additional data added by the call.<br
class="">
<br class="">
JS callbacks cannot be executed directly
from any system context, they need to run in
the Duktape context. So the DuktapeObject
callback invocation mechanism includes a
general method to request a callback
execution by Duktape: RequestCallback()<br
class="">
<br class="">
Note: RequestCallback() is an asynchronous
operation. A synchronous variant can be
added if necessary (and probably will be for
command execution from a console).<br
class="">
<br class="">
A pending callback automatically increments
the reference count, so the object is locked
in memory until the callback has been
executed (or aborted) by the Duktape task.<br
class="">
<br class="">
The callback invocation API provides a void*
for simple data (e.g. a fixed string) to be
passed to the callback method, but for more
complex data, you will normally fill some
DuktapeObject member variables before
invoking the callback.<br class="">
<br class="">
In Duktape context, the callback invocation
translates the data returned or provided by
the system side into the Duktape callback
arguments and then runs the callback (if the
object actually has the requested callback
set). The default implementation for this is
DuktapeObject::CallMethod(), which can be
used directly for simple callbacks without
arguments. For more complex handling,
override this with your custom
implementation.<br class="">
<br class="">
The callbacks are by default executed on the
coupled JS object, so data can also be
transported by setting properties on that
object. The callback can then simply access
them via "this".<br class="">
<br class="">
To simplify callback invocation from code
parts that may run outside or inside
Duktape, it's convenient to allow calling
CallMethod() without a Duktape context, and
let CallMethod() translate that into a
RequestCallback() call as necessary.
Pattern:<br class="">
<br class="">
<tt class="">duk_ret_t
DuktapeHTTPRequest::CallMethod(duk_context
*ctx, const char* method, void* data
/*=NULL*/)</tt><tt class=""><br class="">
</tt><tt class=""> {</tt><tt class=""><br
class="">
</tt><tt class=""> if (!ctx)</tt><tt
class=""><br class="">
</tt><tt class=""> {</tt><tt class=""><br
class="">
</tt><tt class="">
RequestCallback(method, data);</tt><tt
class=""><br class="">
</tt><tt class=""> return 0;</tt><tt
class=""><br class="">
</tt><tt class=""> }</tt><tt class=""><br
class="">
…<br class="">
</tt><br class="">
A CallMethod() implementation isn't limited
to executing a single callback. A common
example is an API defining "done" &
"fail" callbacks, as well as a general final
"always" callback. <tt class="">DuktapeHTTPRequest::CallMethod()</tt>
also serves as an example implementation for
this.<br class="">
<br class="">
<br class="">
<br class="">
Wow… that's become more to write & read
than I expected. Please provide some
feedback: is that explanation sufficient
& clear? I'll refine it for the
developer docs then.<br class="">
<br class="">
Regards,<br class="">
Michael<br class="">
<br class="">
<br class="">
<div class="moz-cite-prefix">Am 01.09.20 um
19:52 schrieb Michael Balzer:<br class="">
</div>
<blockquote type="cite"
cite="mid:c19950a1-cf6e-928f-9ddd-90c444bb3d15@expeedo.de"
class="">
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8"
class="">
Mark,<br class="">
<br class="">
I'll have a look.<br class="">
<br class="">
Regards,<br class="">
Michael<br class="">
<br class="">
<br class="">
<div class="moz-cite-prefix">Am 01.09.20
um 07:30 schrieb Mark Webb-Johnson:<br
class="">
</div>
<blockquote type="cite"
cite="mid:9393E6BF-E1C9-495C-88CE-16082D7678A0@webb-johnson.net"
class="">
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8"
class="">
Michael,
<div class=""><br class="">
</div>
<div class="">Still struggling with
this. It seems like your DuktapeObject
will do this, but I can’t work out how
it works.</div>
<div class=""><br class="">
</div>
<div class="">Here are some notes one
what I have done so far:</div>
<div class=""><br class="">
</div>
<div class="">
<ol class="MailOutline">
<li class="">Created a
stub DuktapeConsoleCommand
(derived from DuktapeObject) in
ovms_duktape.{h,cpp}. This should
hold enough to be able to call the
javascript callback method for
that object. It also stores the
module filename (so the
registration can be removed when
the module is unloaded).<br
class="">
<br class="">
</li>
<li class="">Provide
a DuktapeCommandMap m_cmdmap in
ovms_duktape.{h,cpp} OvmsDuktape
class that stores a mapping from
OvmsCommand to
DuktapeConsoleCommand.<br class="">
<br class="">
</li>
<li class="">Created
a OvmsDuktape::RegisterDuktapeConsoleCommand
in ovms_duktape.{h,cpp) that (a)
creates the OvmsCommand() object,
(b) registers it, (c) creates
the DuktapeConsoleCommand()
object, and (d) updates a map from
OvmsCommand->DuktapeConsoleCommand. There Is also a single
callback DukOvmsCommandRegisterRun
designed to be run by all.<br
class="">
<br class="">
</li>
<li class="">Created
hooks NotifyDuktapeModuleLoad, NotifyDuktapeModuleUnload,
and NotifyDuktapeModuleUnloadAll
in OvmsDuktape. The javascript
module is identified by filename
(path to module or script on vfs,
usually, but may also be an
internal module). The Unload
functions look through the
m_cmdmap and unregister commands
for javascript modules being
unloaded.<br class="">
<br class="">
</li>
<li class="">Provide an
implementation for
ovms_command DukOvmsCommandRegister
to support registering commands
from Javascript modules. This
should extract the details, and
then call
OvmsDuktape::RegisterDuktapeConsoleCommand
to do the actual registration.
This has been implemented, except
for the callback method (and
somehow passing that method from
Javascript in the
OvmsCommand.Register javascript
call).<br class="">
<br class="">
</li>
<li class="">Provide a stub
implementation for <span
style="caret-color: rgb(0, 0,
0);" class="">DukOvmsCommandRegisterRun.
This uses m_cmdmap to lookup
the </span><font class="">DuktapeConsoleCommand
object for the command to be
run. It should execute the
callback method (but that part
is not yet implemented).</font></li>
</ol>
</div>
<div class=""><br class="">
</div>
<div class=""><br class="">
</div>
<div class="">I still need help with #5
and #6. What needs to be implemented
in DuktapeConsoleCommand, and how is
the parameter in <span
style="caret-color: rgb(0, 0, 0);"
class="">OvmsCommand.Register used
to store the callback (#5)? Then how
to callback the command method from</span> <span
style="caret-color: rgb(0, 0, 0);"
class="">DukOvmsCommandRegisterRun</span><span
style="caret-color: rgb(0, 0, 0);"
class=""> (#6)? If you have time, it
is probably much quicker for you to
simply make those changes.</span></div>
<div class=""><span style="caret-color:
rgb(0, 0, 0);" class=""><br class="">
</span></div>
<div class=""><font class="">An alternative
implementation would be to do
something like the pubsub framework,
where the mapping
command->callback is done from
within a javascript module. That I
could do, but it seems your </font><span
style="caret-color: rgb(0, 0, 0);"
class="">DuktapeObject can do it
better.</span><font class=""> </font></div>
<div class=""><br class="">
</div>
<div class="">Thanks, Mark.<br class="">
<div class=""><br class="">
<blockquote type="cite" class="">
<div class="">On 15 Jul 2020, at
3:34 PM, Michael Balzer <<a
href="mailto:dexter@expeedo.de"
class=""
moz-do-not-send="true">dexter@expeedo.de</a>>
wrote:</div>
<br
class="Apple-interchange-newline">
<div class="">
<meta http-equiv="Content-Type"
content="text/html;
charset=UTF-8" class="">
<div class=""> Mark,<br class="">
<br class="">
yes, I needed that persistence
for the HTTP and VFS classes,
but I also needed to be able
to couple a dynamic C++
instance with a JS object and
have a mechanism to prevent
garbage collection while the
C++ side is still in use. If
the C++ side is no longer
needed, the JS finalizer also
needs to imply the C++
instance can be deleted.<br
class="">
<br class="">
That is all implemented by
DuktapeObject. DuktapeObject
also provides JS method
invocation on the coupled JS
object and a mutex for
concurrency protection.<br
class="">
<br class="">
We probably need some more
framework documentation than
the header comments (applies
to all of our framework
classes…):<br class="">
<br class="">
<tt class="">/***************************************************************************************************</tt><tt
class=""><br class="">
</tt><tt class=""> *
DuktapeObject: coupled C++ /
JS object</tt><tt class=""><br
class="">
</tt><tt class=""> * </tt><tt
class=""><br class="">
</tt><tt class=""> * Intended
for API methods to attach
internal API state to a JS
object and provide</tt><tt
class=""><br class="">
</tt><tt class=""> * a
standard callback invocation
interface for JS objects in
local scopes.</tt><tt
class=""><br class="">
</tt><tt class=""> * </tt><tt
class=""><br class="">
</tt><tt class=""> * -
Override CallMethod() to
implement specific method
calls</tt><tt class=""><br
class="">
</tt><tt class=""> * -
Override Finalize() for
specific destruction in JS
context (garbage collection)</tt><tt
class=""><br class="">
</tt><tt class=""> * - call
Register() to prevent normal
garbage collection (but not
heap destruction)</tt><tt
class=""><br class="">
</tt><tt class=""> * - call
Ref() to protect against
deletion (reference count)</tt><tt
class=""><br class="">
</tt><tt class=""> * - call
Lock() to protect concurrent
access (recursive mutex)</tt><tt
class=""><br class="">
</tt><tt class=""> * </tt><tt
class=""><br class="">
</tt><tt class=""> * -
GetInstance() retrieves the
DuktapeObject associated
with a JS object if any</tt><tt
class=""><br class="">
</tt><tt class=""> * - Push()
pushes the JS object onto
the Duktape stack</tt><tt
class=""><br class="">
</tt><tt class=""> * </tt><tt
class=""><br class="">
</tt><tt class=""> * Note:
the DuktapeObject may
persist after the JS object
has been finalized, e.g.</tt><tt
class=""><br class="">
</tt><tt class=""> * if
some callbacks are pending
after the Duktape heap has
been destroyed.</tt><tt
class=""><br class="">
</tt><tt class=""> * Use
IsCoupled() to check if the
JS object is still
available.</tt><tt class=""><br
class="">
</tt><tt class=""> * </tt><tt
class=""><br class="">
</tt><tt class=""> *
Ref/Unref:</tt><tt class=""><br
class="">
</tt><tt class=""> * Normal
life cycle is from
construction to
finalization. Pending
callbacks extend</tt><tt
class=""><br class="">
</tt><tt class=""> * the
life until the last callback
has been processed. A
subclass may extend the life</tt><tt
class=""><br class="">
</tt><tt class=""> * by
calling Ref(), which
increases the reference
count. Unref() deletes the
instance</tt><tt class=""><br
class="">
</tt><tt class=""> * if no
references are left.</tt><tt
class=""><br class="">
</tt><tt class=""> */</tt><tt
class=""><br class="">
</tt><br class="">
You normally just need to use
Register/Deregister &
Ref/Unref, and to implement
the constructor and
CallMethod. Coupling of the
instances normally is done on
construction, as a JS object
is normally already needed for
the parameters and can simply
be attached to.<br class="">
<br class="">
Have a look at
DuktapeHTTPRequest,
DuktapeVFSLoad and
DuktapeVFSSave, these are the
current subclasses using this.<br
class="">
<br class="">
For the command registration I
would probably couple the
OvmsCommand instance with a JS
command object providing an
execution method.<br class="">
<br class="">
Tell me if you need more info.<br
class="">
<br class="">
Regards,<br class="">
Michael<br class="">
<br class="">
<br class="">
<div class="moz-cite-prefix">Am
15.07.20 um 08:12 schrieb
Mark Webb-Johnson:<br
class="">
</div>
<blockquote type="cite"
cite="mid:B291C8AF-8382-4F45-87E3-3DA555E068BE@webb-johnson.net"
class="">
<meta
http-equiv="Content-Type"
content="text/html;
charset=UTF-8" class="">
<div class=""><br class="">
</div>
<div class="">@Michael this
is probably for you.</div>
<div class=""><br class="">
</div>
<div class="">I am trying to
implement javascript
command registration. The
idea is that a javascript
module can call something
like:</div>
<div class=""><br class="">
</div>
<div class="">
<blockquote
style="caret-color:
rgb(0, 0, 0); margin:
0px 0px 0px 40px;
border: none; padding:
0px;" class="">OvmsCommand.Register(basecommand,
name, title, callbackfn,
usage, min, max)</blockquote>
<div style="caret-color:
rgb(0, 0, 0);" class=""><br
class="">
</div>
</div>
<div style="caret-color:
rgb(0, 0, 0);" class="">Then
we reflect that into
MyCommandApp.RegisterCommand,
and keep a track of which
command is for which
javascript callbackfn.
When the command is
executed, we pass it into
duktape.</div>
<div style="caret-color:
rgb(0, 0, 0);" class=""><br
class="">
</div>
<div style="caret-color:
rgb(0, 0, 0);" class="">I
also have tracking for
javascript module loading
and unloading, so I can
DeregisterCommand() if
duktape is reloaded (and
also protected against
commands being registered
in short-lived scripts run
from the command line).</div>
<div style="caret-color:
rgb(0, 0, 0);" class=""><br
class="">
</div>
<div style="caret-color:
rgb(0, 0, 0);" class="">To
implement this, I need to
store the callbackfn as a
persistent reference to a
duktape javascript
function.</div>
<div class=""><br class="">
</div>
<div class="">The issue with
callback function
references in duktape is
summarised here:</div>
<div class=""><br class="">
</div>
<blockquote style="margin: 0
0 0 40px; border: none;
padding: 0px;" class="">
<div class=""><a
href="https://wiki.duktape.org/howtonativepersistentreferences"
class=""
moz-do-not-send="true">https://wiki.duktape.org/howtonativepersistentreferences</a></div>
<div class=""><br class="">
</div>
<div class=""><i class="">When
a Duktape/C function
is called, Duktape
places the call
arguments on the value
stack. While the
arguments are on the
value stack,
they're guaranteed to
be reachable and the
Duktape/C function can
safely work with the
arguments.<br class="">
<br class="">
However, when the
Duktape/C function
returns, the value
stack is unwound and
references in the
function's value stack
frame are lost. If the
last reference to a
particular value was
in the function's
value stack frame, the
value will be garbage
collected when the
function return is
processed.</i></div>
</blockquote>
<div class=""><br class="">
</div>
<div class="">The standard
approach is to store the
reference back in the
duktape duk_push_global_stash
so it won’t get
garbage-collected. But,
that seems messy.</div>
<div class=""><br class="">
</div>
<div class="">I see that
Michael has already
implemented something that
seems similar in
ovms_script.{h, cpp}, for
the async http callbacks.
Presumably to avoid this
issue. But, the approach
seems very different, and
I am not sure if it is
stopping _all_ garbage
collection for the
duration of the async
query, or just that
particular object being
garbage collected. The
work seems extensive
(quite a few objects
involved).</div>
<div class=""><br class="">
</div>
<div class="">So @Michael,
any suggestions for this?
I don’t want to reinvent
the wheel...</div>
<div class=""><br class="">
</div>
<div class="">Regards, Mark.</div>
<br class="">
<fieldset
class="mimeAttachmentHeader"></fieldset>
<pre class="moz-quote-pre" wrap="">_______________________________________________
OvmsDev mailing list
<a class="moz-txt-link-abbreviated" href="mailto:OvmsDev@lists.openvehicles.com" moz-do-not-send="true">OvmsDev@lists.openvehicles.com</a>
<a class="moz-txt-link-freetext" href="http://lists.openvehicles.com/mailman/listinfo/ovmsdev" moz-do-not-send="true">http://lists.openvehicles.com/mailman/listinfo/ovmsdev</a>
</pre>
</blockquote>
<br class="">
<pre class="moz-signature" cols="72">--
Michael Balzer * Helkenberger Weg 9 * D-58256 Ennepetal
Fon 02333 / 833 5735 * Handy 0176 / 206 989 26
</pre>
</div>
_______________________________________________<br class="">
OvmsDev mailing list<br class="">
<a
href="mailto:OvmsDev@lists.openvehicles.com"
class=""
moz-do-not-send="true">OvmsDev@lists.openvehicles.com</a><br
class="">
<a class="moz-txt-link-freetext"
href="http://lists.openvehicles.com/mailman/listinfo/ovmsdev"
moz-do-not-send="true">http://lists.openvehicles.com/mailman/listinfo/ovmsdev</a><br
class="">
</div>
</blockquote>
</div>
<br class="">
</div>
<br class="">
<fieldset class="mimeAttachmentHeader"></fieldset>
<pre class="moz-quote-pre" wrap="">_______________________________________________
OvmsDev mailing list
<a class="moz-txt-link-abbreviated" href="mailto:OvmsDev@lists.openvehicles.com" moz-do-not-send="true">OvmsDev@lists.openvehicles.com</a>
<a class="moz-txt-link-freetext" href="http://lists.openvehicles.com/mailman/listinfo/ovmsdev" moz-do-not-send="true">http://lists.openvehicles.com/mailman/listinfo/ovmsdev</a>
</pre>
</blockquote>
<br class="">
<pre class="moz-signature" cols="72">--
Michael Balzer * Helkenberger Weg 9 * D-58256 Ennepetal
Fon 02333 / 833 5735 * Handy 0176 / 206 989 26</pre>
<br class="">
<fieldset class="mimeAttachmentHeader"></fieldset>
<pre class="moz-quote-pre" wrap="">_______________________________________________
OvmsDev mailing list
<a class="moz-txt-link-abbreviated" href="mailto:OvmsDev@lists.openvehicles.com" moz-do-not-send="true">OvmsDev@lists.openvehicles.com</a>
<a class="moz-txt-link-freetext" href="http://lists.openvehicles.com/mailman/listinfo/ovmsdev" moz-do-not-send="true">http://lists.openvehicles.com/mailman/listinfo/ovmsdev</a>
</pre>
</blockquote>
<br class="">
<pre class="moz-signature" cols="72">--
Michael Balzer * Helkenberger Weg 9 * D-58256 Ennepetal
Fon 02333 / 833 5735 * Handy 0176 / 206 989 26</pre>
</div>
_______________________________________________<br class="">
OvmsDev mailing list<br class="">
<a
href="mailto:OvmsDev@lists.openvehicles.com"
class="" moz-do-not-send="true">OvmsDev@lists.openvehicles.com</a><br
class="">
<a class="moz-txt-link-freetext"
href="http://lists.openvehicles.com/mailman/listinfo/ovmsdev"
moz-do-not-send="true">http://lists.openvehicles.com/mailman/listinfo/ovmsdev</a><br
class="">
</div>
</blockquote>
</div>
<br class="">
</div>
<br class="">
<fieldset class="mimeAttachmentHeader"></fieldset>
<pre class="moz-quote-pre" wrap="">_______________________________________________
OvmsDev mailing list
<a class="moz-txt-link-abbreviated" href="mailto:OvmsDev@lists.openvehicles.com" moz-do-not-send="true">OvmsDev@lists.openvehicles.com</a>
<a class="moz-txt-link-freetext" href="http://lists.openvehicles.com/mailman/listinfo/ovmsdev" moz-do-not-send="true">http://lists.openvehicles.com/mailman/listinfo/ovmsdev</a>
</pre>
</blockquote>
<br class="">
<pre class="moz-signature" cols="72">--
Michael Balzer * Helkenberger Weg 9 * D-58256 Ennepetal
Fon 02333 / 833 5735 * Handy 0176 / 206 989 26</pre>
</div>
_______________________________________________<br
class="">
OvmsDev mailing list<br class="">
<a href="mailto:OvmsDev@lists.openvehicles.com" class=""
moz-do-not-send="true">OvmsDev@lists.openvehicles.com</a><br
class="">
<a class="moz-txt-link-freetext" href="http://lists.openvehicles.com/mailman/listinfo/ovmsdev">http://lists.openvehicles.com/mailman/listinfo/ovmsdev</a><br
class="">
</div>
</blockquote>
</div>
<br class="">
</div>
<br>
<fieldset class="mimeAttachmentHeader"></fieldset>
<pre class="moz-quote-pre" wrap="">_______________________________________________
OvmsDev mailing list
<a class="moz-txt-link-abbreviated" href="mailto:OvmsDev@lists.openvehicles.com">OvmsDev@lists.openvehicles.com</a>
<a class="moz-txt-link-freetext" href="http://lists.openvehicles.com/mailman/listinfo/ovmsdev">http://lists.openvehicles.com/mailman/listinfo/ovmsdev</a>
</pre>
</blockquote>
<br>
<pre class="moz-signature" cols="72">--
Michael Balzer * Helkenberger Weg 9 * D-58256 Ennepetal
Fon 02333 / 833 5735 * Handy 0176 / 206 989 26</pre>
</body>
</html>