Flag an instance "dirty" on field-change?

BlitzMax Forums/BlitzMax Programming/Flag an instance "dirty" on field-change?

Mahan(Posted 2009) [#1]
Hi,

I'm working on a tree-structure based on node-/leaf-types (where each node has a list for the 0..n subnodes as usual)

This tree is to be replicated elsewhere (by DBs/Network) but I only want to send information about the nodes that have actually changed, since last update, due to bandwidth etc. So i.e. I want to flag the changed nodes as "dirty" (as in dirty cache elements) as automatically as possible.

This can be done in these two ways (that i know of):

1) Use reflection on each update (as in 1-per-loop update()-function) and check if something changed against some "old state" or checksum, on each node in the whole tree. (very CPU-time wasting method)

2) Use setters on all fields exclusively and set the dirty flag as soon as some user of this node/leaf updates a value through the setter. (Places responsibility on the implementer of eventual sub-types, to the node/leaf types, to handle the dirtyflag on his/her own.)

Can anyone come up with any other ideas how this could be done better/faster?

edit: clarification


_JIM(Posted 2009) [#2]
I would go with the setter way.

Also, there's no strain on the implementer of a sub-class. You can either handle this in a hidden or final method in your class, or the implementer could simply call "Super.[method]" when overwriting a method.

Hope this helps :)


Mahan(Posted 2009) [#3]
Hmm, I'll try to illustrate the "problem" with a little code.


'This is my base class
Type TTreeNode
  Field dirty:int
  'lots of other cool stuff here
End Type

'This is a derived class made by someone else
Type TPlayer Extends TTreeNode
  Field x:int
  Field y:int
End Type



Now that "someone else" that uses his own TPlayer class still has to do this, to correctly mark TTreeNodes as "dirty", when changing values in an inherited instance:


'This is a derived class used by someone else
Type TPlayer Extends TTreeNode
  Field _x:int
  Field _y:int

  method setx(value:int)
    _x=value
    dirty=True
  end method

  method sety(value:int)
    _y=value
    dirty=True
  end method

End Type



While this is viable its not very convenient and a bit error prone (if there are lots of subclasses and fields the subclassing user might just forget to set dirty at some field and the update breaks)

Other languages like java as an example has "call proxies" that can intercept all calls, and could be used for to manage something like this internally in the base-class, and thats why I'm asking if anyone knows of a mechanism in BlitzMax that could be used for something similar.

Is there a better way to do this, or should I just go for the setter method?


BlitzSupport(Posted 2009) [#4]
Although it's not ideal, the only other way I can think of is to have a TTreeNode.Update function/method that you tell the implementer of sub-classes they must call when finished making changes, eg.

Type TTreeNode

    Global dirty

    Function Update ()
        TTreeNode.dirty = True
    End Function

End Type

Type TPlayer Extends TTreeNode
    Field x
    Field y
End Type

t:TPlayer = New TPlayer

t.x = 100
t.y = 100
t.Update

Print t.dirty


At least they don't have to implement their own methods for each extended type this way, just remember to call the function. I'm not a real OO programmer, though, so this is just a system that seems easier to me!


ImaginaryHuman(Posted 2009) [#5]
If you're doing dirty/clean just use bitmasks - saves a lot of ram.


matibee(Posted 2009) [#6]
I don't see anything wrong with James' code, but to add another option to your list, you could lock and unlock your objects..

Type TTreeNode
    Field locked%, dirty%
    Method Lock ()
        locked = True
    End Method
    Method Unlock ()
        locked = False
        dirty = True
    End Method
End Type

Type TPlayer Extends TTreeNode
    Field x#, y#
    Method setx ( pos# )
        Assert( locked ) ' you could enforce locking in debug build
        If locked x = pos  ' you can prevent change if object not locked
    End Method
End Type


It still can't prevent direct access, but that's a bmax limitation.


Mahan(Posted 2009) [#7]
I'll have to tinker around a bit more and do some speed-tests to see what I can do about this :)

I'll post back if I find a worthwhile solution that is good enough to share.

Thank you all very much for the input!


BlitzSupport(Posted 2009) [#8]
I completely forgot about Abstract -- you could easily force them to define the Set/Get methods when defining extended types:

Type TTreeNode

    ' Whatever...

    Method SetX (value) Abstract

    Method SetY (value) Abstract

End Type



jsp(Posted 2009) [#9]
If you don't mind to have one extra abstract layer in between see the example below:
Only the the main SetX() method does set the Dirty Flag, thus transparent to the user.




_JIM(Posted 2009) [#10]
You could probably use reflection as well without much CPU time wasted (I think).

Type TTreeNode
	Field dirty:Int
	
	Method SetInt(fld:String, val:Int)
		dirty = True
		Local tid:TTypeId = TTypeId.ForObject(Self)
		tid.FindField(fld).SetInt(Self, val)
		
	End Method
	
EndType

Type TPlayer Extends TTreeNode
	Field X:Int
	Field Y:Int
End Type

Local p:TPlayer = New TPlayer

p.SetInt("X", 10)
p.SetInt("Y", 15)

DebugLog(p.X + " , " + p.Y)


There's the downside of having to specifically handle all types (int, float, string, object, etc.)


Mahan(Posted 2009) [#11]

I completely forgot about Abstract -- you could easily force them to define the Set/Get methods when defining extended types:



Yes, but that completely mitigates what I'm trying to accomplish. I probably wasn't clear enough on my purpose:

I wanted to make a TTreeNode that can be extended to any use by another developer aswell i.e. that another dev wants for example TPlayer, TBag, TItem (that in turn is extended to TWeapon, TAmmo ...) etc. each with their own properties for his game. It's impossible for me writing the base-class to foresee what the possible fields that will be necessary for another dev in the future. The TPlayer might need x, y. The Item might require damage, speed etc. The TAmmo might have HitAccuracy etc. (not mentioning the fact that being forced to implement a speed-setter on a TChair might not be what everyone wants in their game)

What I'm leaning to right now is to make a solution where setting dirty on all setters is the speedy, preferred way, but with a fallback solution to a reflection/checksum based "dirtyflagging".

I did something similar in my MaGNet a while ago: http://blitzmax.com/Community/posts.php?topic=81799
There the dirty flag was called "modified"

The difference between MaGNet and this "Distributed Tree" is that I want the new Tree to be able to service a much larger player-base (with "limited node sight" for clients etc.). Larger player-base means lots and lots of nodes so processing time is not trivial anymore :)