gun icon indicating copy to clipboard operation
gun copied to clipboard

Gun.on(cb,true) on multiple levels not working as expected

Open Stefdv opened this issue 7 years ago • 7 comments

I need Gun.on(cb,true) to give me only the changed data ( hence the 'true'). This is working fine on one level. gun.get('livingroom').on( cb , true) . It fails when doing gun.get('livingroom').get('lights').on( cb , true). It will 'trigger' once with only the changed value, but a second time with the full object.

localStorage.clear();
var gun = Gun();
var root = gun.get('root');
console.log('ONE LEVEL');
root.on(function(data, key){
  console.log(data)
},true);
console.log('expect : test:1');
root.put({test:1})
console.log('expect : test:2')
root.put({test:2})
console.log('expect : msg:add')
root.put({msg:'add'})
console.log('expect : test:3')
root.put({test:3})
console.log('expect : msg:add again')
root.put({msg:'add again'})

console.log('\nTWO LEVELS')
var test2 = gun.get('lvl1').get('tests')
test2.on(function(data, key){
  console.log(data)
},true);
console.log('expect : test:1')
test2.put({test:1})
console.log('expect : test:2')
test2.put({test:2})
console.log('expect : msg:add')
test2.put({msg:'add'})
console.log('expect : test:3')
test2.put({test:3})
console.log('expect : msg:add again')
test2.put({msg:'add again'})

Stefdv avatar Dec 29 '17 12:12 Stefdv

Have you tried var test2 = gun.path('lvl1').get('tests')?

dfreire avatar Dec 29 '17 18:12 dfreire

@dfreire well, i use path() myself, but it is not loaded by default...besides that, i don't think that using path() on a single prop makes any difference since all path() is doing is .get().get().get() etc

Oh...and you can never use path() at the beginning

Stefdv avatar Dec 29 '17 19:12 Stefdv

That's interesting, because:

var test2 = gun.get('lvl1').get('tests') has that problem var test2 = gun.get('lvl1').path('tests') has that problem var test2 = gun.path('lvl1').get('tests') seems to work

If path() is just a convenience wrapper around get()why can't it be used in the beginning?

Thanks

dfreire avatar Dec 29 '17 19:12 dfreire

Hmm.. well you might want to look at http://gun.js.org/docs/path.html

Stefdv avatar Dec 29 '17 19:12 Stefdv

I'm seeing behavior where, in the "two levels" case, I get 3x full document upon initialization of the listener, then when I do a .get('somefield').put(123), I get 3 invocations, only one of which is the expected one:

screen shot 2018-03-12 at 11 42 31

however in the top-level case, the only extraneous invocation is the one upon registration:

screen shot 2018-03-12 at 11 44 49

erikkaplun avatar Mar 12 '18 09:03 erikkaplun

@eallik yeah, this is pretty difficult, because there are a certain number of "cycles" that the internal graph representation has to make when data is saved, and each cycle causes its children/proxies to also be called (internally). Without this, some data doesn't get read/notified properly, which is just wrong, however with it, it is pretty hard for me to prevent duplicates.

Clearly though, especially with on(cb, true) (which hasn't gotten much love / testing since the rewrite) this is wrong behavior, so I'll keep the issue open for everyone to see. However, don't expect this to be fixed by v1.0 :( because it is possible to write an extension that more intelligently ignores the extra calls before calling the callback by using some deep compare (however, I won't do this in core, because it is a massive performance hit). There hopefully should be otherways in core for me to deal with it... but low priority :(.

amark avatar Mar 14 '18 03:03 amark

Thanks for leaving this open...I have been struggling to understand this for some time. I ended up not using {change: true} on on() and instead I flatten my object into a single level object of key: value pairs, where the key is the 'path', eg, for the following object hierarchy:

{
  '0': {
    'id': 'a',
    'type': 'node',
    'children': {
      '0': {
        'id': 'aa',
        'type': 'leaf',
        'data': 'first leaf:aa'
      },
      '1': {
        'id': 'ab',
        'type': 'folder',
        'children': {
          '0': {
            'id': 'aba',
            'type': 'leaf',
            'data': 'second leaf:aba'
          }
        }
      }
    }
  },
  '1': {
    'id': 'b',
    'type': 'leaf',
    'data': 'third leaf:b'
  }
}

(The object I want to 'sync' between peers is like the above) When it is flattened, it is like this:

"0.id": "a"
"0.type": "node"
"0.children.0.id": "aa"
"0.children.0.type": "leaf"
"0.children.0.data": "first leaf:aa"
"0.children.1.id": "ab"
"0.children.1.type": "folder"
"0.children.1.children.0.id": "aba"
"0.children.1.children.0.type": "leaf"
"0.children.1.children.0.data": "second leaf:aba"
"1.id": "b"
"1.type": "leaf"
"1.data": "third leaf:b"

I want a callback when a peer changes (modifies, deletes, adds) to the above structure, and I couldn't get get("testing").on(console.log, {change: true}); to make much sense. It seemed to always return the whole thing no matter what was changed.

However, I did seem to get some sense from:

get("testing").map().on((data, key) => console.log({key, data}));

That seems to create an 'on()' for each of the properties, and also create new ones whenever a new property is created. That seems kind of inefficient (akin to placing an eventListener on each item on a <li> compared to one on the <ol> and the like); and I have yet to see if it has any performance problems with large objects.

I am also scratching my head that the current client's on() handlers are invoked when the current client does a put()...is there no way to tell Gun that I only want to know about changes made by other peers, not by this one.

This all does seem rather complicated for such a basic use-case - ie wanting to sync an existing object between peers. Is there some documentation for how to use Gun for this use-case?

davidmaxwaterman avatar Oct 24 '22 17:10 davidmaxwaterman