[Ovmsdev] Duktape Persistent Function References

Michael Balzer dexter at expeedo.de
Wed Sep 2 01:52:12 HKT 2020


Mark,

I'll have a look.

Regards,
Michael


Am 01.09.20 um 07:30 schrieb Mark Webb-Johnson:
> Michael,
>
> Still struggling with this. It seems like your DuktapeObject will do
> this, but I can’t work out how it works.
>
> Here are some notes one what I have done so far:
>
>  1. 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).
>
>  2. Provide a DuktapeCommandMap m_cmdmap in
>     ovms_duktape.{h,cpp} OvmsDuktape class that stores a mapping from
>     OvmsCommand to DuktapeConsoleCommand.
>
>  3. 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.
>
>  4. 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.
>
>  5. 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).
>
>  6. Provide a stub implementation for DukOvmsCommandRegisterRun. This
>     uses m_cmdmap to lookup the DuktapeConsoleCommand object for
>     the command to be run. It should execute the callback method (but
>     that part is not yet implemented).
>
>
>
> I still need help with #5 and #6. What needs to be implemented
> in DuktapeConsoleCommand, and how is the parameter
> in OvmsCommand.Register used to store the callback (#5)? Then how to
> callback the command method from DukOvmsCommandRegisterRun (#6)? If
> you have time, it is probably much quicker for you to simply make
> those changes.
>
> 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 DuktapeObject
> can do it better. 
>
> Thanks, Mark.
>
>> On 15 Jul 2020, at 3:34 PM, Michael Balzer <dexter at expeedo.de
>> <mailto:dexter at expeedo.de>> wrote:
>>
>> Mark,
>>
>> 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.
>>
>> That is all implemented by DuktapeObject. DuktapeObject also provides
>> JS method invocation on the coupled JS object and a mutex for
>> concurrency protection.
>>
>> We probably need some more framework documentation than the header
>> comments (applies to all of our framework classes…):
>>
>> /***************************************************************************************************
>>  * DuktapeObject: coupled C++ / JS object
>>  *
>>  *  Intended for API methods to attach internal API state to a JS
>> object and provide
>>  *    a standard callback invocation interface for JS objects in
>> local scopes.
>>  * 
>>  *  - Override CallMethod() to implement specific method calls
>>  *  - Override Finalize() for specific destruction in JS context
>> (garbage collection)
>>  *  - call Register() to prevent normal garbage collection (but not
>> heap destruction)
>>  *  - call Ref() to protect against deletion (reference count)
>>  *  - call Lock() to protect concurrent access (recursive mutex)
>>  * 
>>  *  - GetInstance() retrieves the DuktapeObject associated with a JS
>> object if any
>>  *  - Push() pushes the JS object onto the Duktape stack
>>  * 
>>  *  Note: the DuktapeObject may persist after the JS object has been
>> finalized, e.g.
>>  *    if some callbacks are pending after the Duktape heap has been
>> destroyed.
>>  *    Use IsCoupled() to check if the JS object is still available.
>>  * 
>>  *  Ref/Unref:
>>  *    Normal life cycle is from construction to finalization. Pending
>> callbacks extend
>>  *    the life until the last callback has been processed. A subclass
>> may extend the life
>>  *    by calling Ref(), which increases the reference count. Unref()
>> deletes the instance
>>  *    if no references are left.
>>  */
>>
>> 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.
>>
>> Have a look at DuktapeHTTPRequest, DuktapeVFSLoad and DuktapeVFSSave,
>> these are the current subclasses using this.
>>
>> For the command registration I would probably couple the OvmsCommand
>> instance with a JS command object providing an execution method.
>>
>> Tell me if you need more info.
>>
>> Regards,
>> Michael
>>
>>
>> Am 15.07.20 um 08:12 schrieb Mark Webb-Johnson:
>>>
>>> @Michael this is probably for you.
>>>
>>> I am trying to implement javascript command registration. The idea
>>> is that a javascript module can call something like:
>>>
>>>     OvmsCommand.Register(basecommand, name, title, callbackfn,
>>>     usage, min, max)
>>>
>>>
>>> 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.
>>>
>>> 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).
>>>
>>> To implement this, I need to store the callbackfn as a persistent
>>> reference to a duktape javascript function.
>>>
>>> The issue with callback function references in duktape is summarised
>>> here:
>>>
>>>     https://wiki.duktape.org/howtonativepersistentreferences
>>>
>>>     /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.
>>>
>>>     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./
>>>
>>>
>>> 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.
>>>
>>> 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).
>>>
>>> So @Michael, any suggestions for this? I don’t want to reinvent the
>>> wheel...
>>>
>>> Regards, Mark.
>>>
>>> _______________________________________________
>>> OvmsDev mailing list
>>> OvmsDev at lists.openvehicles.com
>>> http://lists.openvehicles.com/mailman/listinfo/ovmsdev
>>
>> -- 
>> Michael Balzer * Helkenberger Weg 9 * D-58256 Ennepetal
>> Fon 02333 / 833 5735 * Handy 0176 / 206 989 26
>> _______________________________________________
>> OvmsDev mailing list
>> OvmsDev at lists.openvehicles.com <mailto:OvmsDev at lists.openvehicles.com>
>> http://lists.openvehicles.com/mailman/listinfo/ovmsdev
>
>
> _______________________________________________
> OvmsDev mailing list
> OvmsDev at lists.openvehicles.com
> http://lists.openvehicles.com/mailman/listinfo/ovmsdev

-- 
Michael Balzer * Helkenberger Weg 9 * D-58256 Ennepetal
Fon 02333 / 833 5735 * Handy 0176 / 206 989 26

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.openvehicles.com/pipermail/ovmsdev/attachments/20200901/aa322f53/attachment.htm>


More information about the OvmsDev mailing list