<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class=""><blockquote type="cite" class=""><div 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.</div></blockquote><div class=""><br class=""></div>Ok. That is simple.<div class=""><br class=""></div><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><div class=""><br class=""></div><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><div class=""><br class=""></div><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><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="">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">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">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 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="">OvmsDev@lists.openvehicles.com</a><br class="">http://lists.openvehicles.com/mailman/listinfo/ovmsdev<br class=""></div></blockquote></div><br class=""></div></body></html>