[x3d-public] Routes with JS Proxy

Andreas Plesch andreasplesch at gmail.com
Mon Apr 17 08:47:09 PDT 2017


Hi John,

I do plan to look at the code and understand it better, ideally in the
context of a simple json encoded x3d scene though it may be some time.

To recap, I think the idea is to perhaps use this to have a dynamically
updating scene as json (or js?) object which then would be (dynamically?)
translated to a-frame (or x3dom, or cobweb?) ?

Since you mentioned proxied get functions, an idea may be to use the proxy
to transparently return actual field values rather than strings( or arrays,
or numbers) though I am not sure if there is an advantage over having an
explicit getField function.

-Andreas

On Mon, Apr 17, 2017 at 3:12 AM, <yottzumm at gmail.com> wrote:

> Here’s a version without a scenegraph which almost does what you want.
>
>
>
>    1. It doesn’t have full paths to fields, but that could be added
>    easily I think.  I have similar code to the original code running, by
>    uncommenting some parts dealing with MFStrings.  Note that you don’t send
>    the proxies to the routes in this case, since I don’t think they are deep
>    objects (well, I’m not sure if I checked).  You could send proxies.  I am
>    not sure if putting proxies in the scenegraph works or not yet.
>    2. It uses proxies in the routes.   I am not sure how pulling values
>    out of proxies works, but I have attempted that.  There may be issues here.
>    3. The proxy’s values aren’t up to date when they get set (bad, I
>    think).
>    4. The original nodes are changed, despite not being in the routes.
>    5. Route chaining is working
>    6. Normal JavaScript variable setting on the proxy works—setField is
>    not called (but the event is not fired on the node, I don’t think, but I
>    might have seen it once).  setField was used to convert an MFString path
>    into a property (see commented out bit).  Conceivably, if setting the node
>    works, we could hide the proxy stuff efficiently I think—but to my
>    knowledge, it doesn’t.
>    7. I added get functions to the Proxies.  Not sure if I should have or
>    not.  It doesn’t seem to hurt, but I could be doing it wrong.
>
>
>
>
>
> https://github.com/coderextreme/X3DJSONLD/blob/master/singleroute.js
>
>
>
> Maybe with some work, this version can get whipped into shape.  I think I
> want to support multiple routes on the same fromField (not supported yet).
>
>
>
> Do people think of from fields as paths as a good thing.
>
>
>
> Another pair of eyes on this would be good.
>
>
>
> John
>
> Sent from Mail <https://go.microsoft.com/fwlink/?LinkId=550986> for
> Windows 10
>
>
>
> *From: *yottzumm at gmail.com
> *Sent: *Monday, April 17, 2017 12:41 AM
> *To: *Andreas Plesch <andreasplesch at gmail.com>
> *Cc: *X3D Graphics public mailing list <x3d-public at web3d.org>
> *Subject: *RE: Routes with JS Proxy
>
>
>
> Actually, I was thinking if you put the proxy in the scenegraph, it would
> allow for event chaining.  I will look into it.
>
> I just can’t set the fromField in the event handler.
>
>
>
> John
>
>
>
> Sent from Mail <https://go.microsoft.com/fwlink/?LinkId=550986> for
> Windows 10
>
>
>
> *From: *Andreas Plesch <andreasplesch at gmail.com>
> *Sent: *Saturday, April 15, 2017 4:13 PM
> *To: *John Carlson <yottzumm at gmail.com>
> *Cc: *X3D Graphics public mailing list <x3d-public at web3d.org>
> *Subject: *Re: Routes with JS Proxy
>
>
>
> Hi John,
>
>
>
> thanks, I am trying to follow what this is doing and copied the code here:
>
>
>
> https://gist.github.com/andreasplesch/59da818dbb4b7bc55c4c1aed401faa7c
>
>
>
> I am imagining that after creating a Route with route(), the proxies would
> automatically ensure that if a value in the js object derived from the JSON
> is modified (set) (in any way), the routed to property value is also
> modified immediately. Is that the idea ?.
>
>
>
> But then there are custom, public and private setField, and
> setInternalField functions ? Would it be possible to avoid those and use
> direct js object manipulation functionality ?
>
>
>
> Here is the short line on how addRoute is defined in the SAI (as a
> function of an executioncontext):
>
>
>
> http://www.web3d.org/documents/specifications/
> 19777-1/V3.3/Part1/functions.html#t-ExecutionContextFunctions
>
>
>
> Do you think it would be possible to do this directly for DOM (or XML
> encoded) scenes as well ? I think it could be since the json path
> translates to .querySelector and setField to .setAttribute and .addChild
>
>
>
> Generally, I think using Proxy as a mechanism to trap setting values and
> then forwarding those could be a good idea allowing separation from DOM
> events.
>
>
>
> -Andreas
>
>
>
>
>
>
>
> Date: Sat, 15 Apr 2017 11:45:57 -0400
> From: <yottzumm at gmail.com>
> To: X3D Graphics public mailing list <x3d-public at web3d.org>
> Subject: [x3d-public] (no subject)
> Message-ID: <58f24034.8f2e6b0a.7ce43.ba74 at mx.google.com>
> Content-Type: text/plain; charset="utf-8"
>
> Inclosed is my first attempt at a new style of routes and events with
> JavaScript.  Routes are MFString path
> Down the JS object hierarchy.  Once the proxy and routes are set up, you
> can set values in the proxy, and the scenegraphs will change.  Please see
> my test cases and add more!  It does not handle script functions yet, but
> there is a path forward.  Perhaps someone will look at this code and find
> something of value.
>
> John
>
> /***********************************************************
> *****************/
> 'use strict';
> /*
> Copyright (c) 2017, John Carlson
> All rights reserved.
>
> Redistribution and use in source and binary forms, with or without
> modification, are permitted provided that the following conditions are met:
>
> * Redistributions of source code must retain the above copyright notice,
> this
>   list of conditions and the following disclaimer.
>
> * Redistributions in binary form must reproduce the above copyright notice,
>   this list of conditions and the following disclaimer in the documentation
>   and/or other materials provided with the distribution.
>
> * Neither the name of content nor the names of its
>   contributors may be used to endorse or promote products derived from
>   this software without specific prior written permission.
>
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> ARE
> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
> LIABLE
> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
> LIABILITY,
> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
> USE
> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
> */
>
>
> test();
>
> /**
>  * Override this to get rid of self-test code
>  * proxyAction should be paired with a scenegraph exclusively
>  */
> function test() {
>         let fromScenegraph = [ {d: { f: 7 , e: [1, 2, 3]}}, { c : [4]}]
>         let toScenegraph = fromScenegraph;
>         info("Scenegraph originally "+stringify(fromScenegraph));
>         assert(fromScenegraph, toScenegraph);
>         let proxyAction = {};
>         let proxy = createProxy(proxyAction, fromScenegraph);
>
>         // Create actionable fields in the fromScenegraph, that set
>         // fields in the toScenegraph
>         //
>         // path are MFStirngs which follw a path down the scenegraph, one
>         // element at a time.  The final SFSTring is thei field to modify.
>         //
>         // This does not handle script nodes yet (both getting and setting
>         // values, but could possibly be handled by modifying
> SetInternalField.
>         //
>         route(proxyAction,
>                 fromScenegraph, '"0" "d" "e" "0"',
>                 toScenegraph, '"1" "c"');
>         route(proxyAction,
>                 fromScenegraph, '"0" "d"',
>                 toScenegraph, '"1"');
>         route(proxyAction,
>                 fromScenegraph, '"0" "A"',
>                 toScenegraph, '"0" "B"');
>         // no changes to scenegraph yet
>         assert(fromScenegraph, [{"d":{"f":7,"e":[1,2,3]}},{"c":[4]}]);
>
>         setField(proxy, '"0" "d" "e" "0"', 5);
>         assert(fromScenegraph, [{"d":{"f":7,"e":[5,2,3]}},{"c":5}]);
>         setField(proxy, '"0" "d" "e" "1"', 8);
>         assert(fromScenegraph, [{"d":{"f":7,"e":[5,8,3]}},{"c":5}]);
>         setField(proxy, '"0" "d"', 6);
>         assert(fromScenegraph, [{"d":6},6]);
>         setField(proxy, '"0" "d"', 9);
>         assert(fromScenegraph, [{"d":9},9]);
>         // add field'
>         setField(proxy, '"0" "A"',  10);
>         assert(fromScenegraph, [{"d":9,"B":10,"A":10},9]);
>         setField(proxy, '"0" "A"', [ "Test!" ]);
>         assert(fromScenegraph, [{"d":9,"B":["Test!"],"A":["Test!"]},9]);
>         setField(proxy, '"0" "B"', null);
>         assert(fromScenegraph, [{"d":9,"B":null,"A":["Test!"]},9]);
>         setField(proxy, '"0" "A" "0" "0"', ["Cr"]);  // set a part of a
> string
>         assert(fromScenegraph, [{"d":9,"B":null,"A":["Crest!"]},9]);
>         setField(proxy, '"0" "A"', { "/": "/" }); // now A and B point to
> same
>         assert(fromScenegraph, [{"d":9,"B":{"/":"/"},"A":{"/":"/"}},9]);
>         setField(proxy, '"0" "A" "/"', "\\");
>         assert(fromScenegraph, [{"d":9,"B":{"/":"\\"},"A":{"/":"\\"}},9]);
>         setField(proxy, '"0" "A"', { "\\": "\\" }); // now A and B point
> to same
>         assert(fromScenegraph, [{"d":9,"B":{"\\":"\\"},"A":{"
> \\":"\\"}},9]);
>         setField(proxy, '"0" "A" "\\"', "]");
>         assert(fromScenegraph, [{"d":9,"B":{"\\":"]"},"A":{"\\":"]"}},9]);
>         setField(proxy, '"0" "A"', { "]" : "]" }); // now A and B point to
> same
>         assert(fromScenegraph, [{"d":9,"B":{"]":"]"},"A":{"]":"]"}},9]);
>         setField(proxy, '"0" "A" "]"', "[");
>         assert(fromScenegraph, [{"d":9,"B":{"]":"["},"A":{"]":"["}},9]);
>         setField(proxy, '"0" "A"', { "[" : "[" }); // now A and B point to
> same
>         assert(fromScenegraph, [{"d":9,"B":{"[":"["},"A":{"[":"["}},9]);
>         setField(proxy, '"0" "A" "["', "][");
>         assert(fromScenegraph, [{"d":9,"B":{"[":"]["},"A":{"[":"]["}},9]);
>         setField(proxy, '"0" "A"', { "][" : "][" }); // now A and B point
> to same
>         assert(fromScenegraph, [{"d":9,"B":{"][":"]["},"A":{"
> ][":"]["}},9]);
>         setField(proxy, '"0" "A" "]["', "][][" );
>         assert(fromScenegraph, [{"d":9,"B":{"][":"][]["},"A":
> {"][":"][]["}},9]);
>         setField(proxy, '"0" "A"', { "][][" : "][][" }); // now A and B
> point to same
>         assert(fromScenegraph, [{"d":9,"B":{"][][":"][]["},"
> A":{"][][":"][]["}},9]);
>         setField(proxy, '"0" "A" "][]["',   "[][]");
>         assert(fromScenegraph, [{"d":9,"B":{"][][":"[][]"},"
> A":{"][][":"[][]"}},9]);
>         setField(proxy, '"0" "A"', { "[][]" : "[][]" }); // now A and B
> point to same
>         assert(fromScenegraph, [{"d":9,"B":{"[][]":"[][]"},"
> A":{"[][]":"[][]"}},9]);
>         setField(proxy, '"0" "A" "[][]"', "C");
>         assert(fromScenegraph, [{"d":9,"B":{"[][]":"C"},"A":{
> "[][]":"C"}},9]);
>         assert(fromScenegraph, toScenegraph);
>         info("Scenegraph finally "+stringify(fromScenegraph));
> }
>
> function setField(proxy, fromPath, value) {
>         let selector = MFStringToProperty(fromPath);
>         raw("\n");
>         info("Storing "+fromPath+" = "+selector+" = "+stringify(value)+"
> in Proxy");
>         proxy[selector] = value;
> }
>
> /**
>  * Override this if you don't want test warnings about scenegraphs
>  */
>
> function assert(modifiedScenegraph, goldenScenegraph) {
>         var mod = stringify(modifiedScenegraph);
>         var testcase = stringify(goldenScenegraph);
>         if (mod !== testcase) {
>                 fatal("Scenegraph "+mod)
>                 fatal("        != "+testcase);
>         } else {
>                 debug("Scenegraph "+mod+" == "+testcase);
>                 info("TEST PASSED");
>         }
> }
>
> /**
>  * Pass in a JSON parseable object be stringified,
>  * or a regular JSON object.  Or a string to be parsed.
>  * selector returned the is the path into the proxy.
>  *
>  * This should normalize the string, but there may be issues
>  * with ordering objects.
>  */
> function stringify(selectorPath) {
>         if (typeof selectorPath === 'string') {
>                 debug("selector output "+selectorPath);
>                 return selectorPath;
>         /*
>                 let indexes = parse(selectorPath);
>                 let selector = JSON.stringify(indexes);
>                 debug("selector output "+selector);
>                 return selector;
>         */
>         } else {
>                 let selector = JSON.stringify(selectorPath);
>                 debug("selector output "+selector);
>                 return selector;
>         }
> }
>
> /**
>  * breaks up selectorPath into component pieces and returns them
>  */
> function parse(selectorPath) {
>         debug("selector input "+selectorPath);
>         if (typeof selectorPath === 'string') {
>                 let indexes = JSON.parse(selectorPath);
>                 return indexes;
>         } else {
>                 return selectorPath;
>         }
> }
>
> function MFStringToProperty(string) {
>         debug("MFString input "+string+" "+typeof string);
>         string = string.replace(/" "/g, ',');
>         string = string.substr(1, string.length-2);
>         return string;
> }
>
> /*
> function JsonToMFString(json) {
>         let str = stringify(json).split(/,/).join('" "')
>         str = "'"+str.substr(1, str.length-2)+'"';
>         return str;
> }
> */
>
> function PropertyToJson(string) {
>         debug("property input "+string+" "+typeof string);
>         return string.split(/,/);
> }
>
> /** override this function if you want a feature other than a raw message
>  */
> function raw(string) {
>         console.log(string);
> }
>
> /** override this function if you want a feature other than a fatal message
>  */
> function fatal(string) {
>         console.error("FATAL: "+string);
> }
>
> /** override this function if you want a feature other than a warning
> message
>  */
> function warning(string) {
>         console.error("============ WARNING: "+string);
> }
>
> /** override this function if you want a feature other than console.log or
> to
>  * disable this function
>  */
> function debug(string) {
>         // raw(string);
> }
>
> /** override this function if you want a feature other than console.log or
> to
>  * disable this function
>  */
> function info(string) {
>         raw("****** "+string);
> }
>
> /**
>  * setInternalField() --  set a field in a scenegraph internally.  Use
>  * setField() and set up routes and the proxy object on the scenegraph so
>  * events flow.
>  * Override this if you want a different selector language.
>  * The scenegraph is the javascript object to set the path to value on.
>  * selectorPath is a JSON array path of keys and indexes into the
> scenegraph.
>  *      You may use quotes to backslashes to escape things
>  * The value is set in the scenegraph at the selectorPath location
>  *
>  * The client may want the select to impact several objects.  That is up
>  * to the implementer of setInternalField().  The selectorPath affects the
>  * scenegraph.
>  * Right now we just have a simple JavaScript implementation.  Something
> like
>  * JSONPath is realizable in this framework I think.
>  *
>  * You should not use this method to set values on the proxy.
>  *
>  * Calls:
>  *      stringify: to return a string for viewing.
>  *      parse: to return javascript object indexes as a selector.
>  */
> function setInternalField(scenegraph, selectorPath, value) {
>         debug("Scenegraph before "+ stringify(scenegraph));
>         let skipDescendants = 0; // number of descendents to skip
>         let selectedValue = scenegraph;
>         let higherValue = selectedValue;
>         var selector  = PropertyToJson(selectorPath);
>         let depth = (selector.length - skipDescendants);
>         debug("Trying to Set "+stringify(scenegraph)+stringify(selector)+"
> = "+
>                 stringify(value));
>         for (var index = 0; index < depth - 1; index++) {
>                 debug("Index "+index+" is "+selector[index]);
>                 higherValue = selectedValue;
>                 debug("Previous downselected selectedValue === "+
>                         stringify(selectedValue));
>                 debug("Index "+index+" is "+selector[index]);
>                 debug("New Selected Value === "+selectedValue[selector[
> index]]);
>                 selectedValue = selectedValue[selector[index]];
>                 debug("Now downselected selectedValue === "+selectedValue);
>         }
>         if (typeof value === 'string') {
>                 value = value.replace(/\\\\/g, '\u005c');
>         }
>         debug("Index "+index+" is "+selector[index]);
>         /**
>          * This is the code that has to change to call functions in X3D
> Scripts
>          * fields.   For toField, I would check to make sure the LHS is a
>          * function, and pass a value to the function (along with a
> timestamp),
>          * for From Field, I would make sure value is a function, and call
> it
>          * with a timestamp.
>          */
>         if (typeof selectedValue === 'string') {
>             var str = selectedValue.split('');
>                     info("Setting "+ stringify(higherValue) +
>                         "[" + selector[depth-2] + "] = "+
>                         stringify(value));
>                     str[selector[depth-1]] = value;
>                     higherValue[selector[depth-2]] = str.join('');
>                     // unless there's more than one
>         } else {
>                 info("Setting "+
>                         stringify(selectedValue) +
>                         "[" + selector[depth-1] + "] = "+
> stringify(value));
>                 selectedValue[selector[depth-1]] = value;
>                 // unless there's more than one
>         }
>         raw("RESULT scenegraph "+stringify(scenegraph));
>         return true;
> }
>
>
> /**
>  * This proxy works on individual values in a scenegraph, because a shadow
>  * scenegraph is keep with fromPaths kept as proxy objects.
>  *
>  * If a route hasn't been set up yet, then the toScenegraph is not
> affected,
>  * unless it's a part of the fromSceneGraph.
>  */
>
> function createProxy(proxyAction, fromScenegraph) {
>         var proxy = new Proxy(proxyAction, {
>                 set : function(target, property, value, receiver) {
>                         debug("Value set is "+value);
>                         debug("property is "+ property);
>                         for (let action in proxyAction) {
>                                 debug(" "+action+" "+typeof
> proxyAction[property]);
>                         }
>                         if (typeof proxyAction[property] === 'function') {
>                                 // set the toScenegraph, act on the route
>                                 proxyAction[property](property, value);
>                         } else {
>                                 warning("Failed to set value on
> toScenegraph (no route)");
>                         }
>                         return setInternalField(fromScenegraph, property,
> value);
>                 }
>         });
>         return proxy;
> }
>
> /**
>  * Proxy action, set on route.   This should only be called when a route
>  * is in place.  It's private an should not be called by others.
>  * Scenegraphs are JavaSCript objects
>  * Paths are selectors into scenegraphs.
>  * property is the property being set and should be equal to fromPath
>  * value is the value being set on the toScenegraph.   To set the
>  * fromScenegraph, use setField() on the proxy or setInternalField, if you
> don'
>  * want to affect the toScenegraph.
>  */
> function proxySetAction(fromScenegraph, fromPath, toScenegraph, toPath,
> property, value) {
>         let fromProperty = MFStringToProperty(fromPath);
>         if (fromProperty != property) {
>                 fatal("from"+fromPath+" out of sync with property
> "+property+".  Did you forget to set a route?");
>         }
>         debug("fromPath is "+fromPath);
>         debug("toPath is "+toPath);
>         debug("property is "+property);
>         let toProperty = MFStringToProperty(toPath);
>         debug("toProperty is "+toProperty);
>         if (toScenegraph == fromScenegraph && fromPath == toPath) {
>                 warning("We don't need to set the same value twice!");
>         } else {
>                 setInternalField(toScenegraph, toProperty, value);
>         }
>
>         return true;
> }
>
> /**
>  * Activate a proxy route from fromPath to toPath
>  * The proxy map is a private object which must be passed around.  I will
> later
>  * make it unaccessible.
>  * Paths are selectors which can be used with setInternalField()
>  * fromScenegraph and toScenegraph may be separate.  If they are the
>  * same, be sure that fromField and toField are distinct, or else the
>  * results may be undetermined.
>  */
> function route(proxyAction, fromScenegraph, fromPath, toScenegraph,
> toPath) {
>         info("<ROUTE fromPath='"+ fromPath+ "' "+ "toPath='"+ toPath+ "'/>"
>                 );
>         if (fromScenegraph === toScenegraph &&
>                 fromPath.startsWith(toPath) &&
>                 toPath.startsWith(fromPath) &&
>                 fromPath !== toPath) {
>                 warning("possible undetermined behavior, fromPath
> "+fromPath+" and toPath "+toPath+" overlap and the scenegraphs are the
> same");
>         }
>         proxyAction[MFStringToProperty(fromPath)] = function(property,
> value) {
>                 return proxySetAction(fromScenegraph, fromPath,
>                         toScenegraph, toPath,
>                         property, value);
>         };
> }
> -------------- next part --------------
> An HTML attachment was scrubbed...
> URL: <http://web3d.org/pipermail/x3d-public_web3d.org/
> attachments/20170415/50b036e8/attachment.html>
>
> ------------------------------
>
> Subject: Digest Footer
>
> _______________________________________________
> x3d-public mailing list
> x3d-public at web3d.org
> http://web3d.org/mailman/listinfo/x3d-public_web3d.org
>
>
> ------------------------------
>
> End of x3d-public Digest, Vol 97, Issue 38
> ******************************************
>
>
>
>
>
> --
>
> Andreas Plesch
> 39 Barbara Rd.
> Waltham, MA 02453
>
>
>
>
>



-- 
Andreas Plesch
39 Barbara Rd.
Waltham, MA 02453
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://web3d.org/pipermail/x3d-public_web3d.org/attachments/20170417/d32f79c0/attachment-0001.html>


More information about the x3d-public mailing list