DOM Node Data Inheritance?

37 views
Skip to first unread message

Jason Batchkoff

unread,
Dec 10, 2014, 3:40:24 PM12/10/14
to authoring-to...@googlegroups.com
Hey guys, I was curious if DOM supported inherited data.  I'll set up an example to help explain:

Let's say I have:

class Foo
{
     float Health;
     string Model;
}

And I have two objects:

Foo Bob = new Foo{ Health = 100.0f; Model = "human.mdl" };
Foo Alice = new Foo();

Can I somehow base Alice's data on Bob?  I'm thinking the following:

// set up Alice to use all of Bob's data if Alice doesn't pointedly 'override' it.  
// i.e., Alice's fields' defaults are now set to Bob's values.
Alice.Base = Bob;                             

Alice.Health = 200.0f;

Console.WriteLine(Alice.Model); // should print out "human.mdl"
Console.WriteLine(Alice.Health); // should print out "200.0", since Alice has overridden her version of the data.

Alice.Health.ResetToDefault();
Console.WriteLine(Alice.Health); // should print out "100.0", since that is what Bob's health is.

Bob.Health.ResetToDefault();
Console.WriteLine(Alice.Health); // should now print "0.0", since Bob's health is now 0.

Does this make any sense?  Does DOM support this?  If not, how could I go about implementing it?

Ron2

unread,
Dec 10, 2014, 9:10:27 PM12/10/14
to authoring-to...@googlegroups.com
Hi Jason,

This is an interesting problem that I haven't seen anyone implement. DomNodes have default values for their attributes, so, assuming the DOM is useful for you for other purposes (persistence, undo/redo, copy/paste) then you could certainly use DomNodes to store each objects' data. However, the DomNode attribute default is for a type (DomNodeType, not C# type) of DomNode and in your example, the type of both Alice and Bob is Foo. So, you would still need some special logic to inherit the property values.

A sort-of similar technique that has been useful and that we implemented in an old version of our LevelEditor, is a concept that we called Prefabs. Prefabs are types of objects and they would appear in a palette in your tool, and the user could drag and drop the Prefab into the design view to create Prefab Instances. When a Prefab was modified to have a new value for a property like Health, then all the Prefab Instances would receive the update unless the user had specifically modified that property of ta Prefab Instance. The user could also make copies of Prefabs that derive from each other and appear in the palette too, and the derived Prefab could change the default value of properties. So you could have a Prefab called Orc and a derived Prefab called OrcBoss that had a higher Health value. And you might have many instances of Orc and OrcBoss in your level and you could easily change the default Health on them.

I just want to make sure that what you want is inheritable properties on individual objects rather than inheritable properties on types of objects.

So, to do what you're trying to do, I think you'll need some kind of new InheritableProperty class that will help you get the correct value of the C# property. If you use the DOM, then this InheritableProperty class can be given the base DomNode (which would become available when setting Base in your example) and the AttributeInfo that corresponds to the base object's matching property. In your Health getter, the InheritableProperty could simply be asked for the value. It would check if the value of the DomNode attribute was at its default using DomNode.IsAttributeDefault(AttributeInfo). If so, then it would check if there was a base DomNode and then get the attribute's value. Here's a mock-up that compiles, but I haven't tested it, to show you what I mean. I used Height instead of Health because I happened to have an AttributeInfo handy called height.


    public class InheritableProperty
    {
        public InheritableProperty(AttributeInfo attribute)
        {
            m_attribute = attribute;
        }

        public DomNode BaseNode
        {
            get { return m_baseNode; }
            set { m_baseNode = value; }
        }

        public object GetValue(DomNode ourNode)
        {
            if (m_baseNode != null && ourNode.IsAttributeDefault(m_attribute))
                return m_baseNode.GetAttribute(m_attribute);
            return ourNode.GetAttribute(m_attribute);
        }

        public void SetValue(DomNode ourNode, object value)
        {
            ourNode.SetAttribute(m_attribute, value);
        }

        private DomNode m_baseNode;
        private readonly AttributeInfo m_attribute;
    }

    public class Foo : DomNodeAdapter
    {
        public Foo()
        {
            m_height = new InheritableProperty(Schema.annotationType.heightAttribute);
        }

        /// <summary>
        /// Gets or sets the base Foo object to be used for inheritable property values. If null,
        /// then this Foo object is used. Otherwise, the base Foo object is used for the value
        /// unless a particular property of ours (e.g., Height) has been changed.</summary>
        public Foo Base
        {
            get { return m_base; }
            set
            {
                m_base = value;
                
                // Be sure to set the BaseNode of all the properties that need it!
                m_height.BaseNode = value.As<DomNode>();
            }
        }

        public int Height
        {
            get { return (int)m_height.GetValue(DomNode); }
        }

        private Foo m_base;
        private readonly InheritableProperty m_height;
    }

I hope I've understood what you're trying to do and that this helps!

--Ron

Jason Batchkoff

unread,
Dec 11, 2014, 1:16:28 PM12/11/14
to
Hey Ron,

Yes, I'm pretty sure I'm talking about something similar to the Prefab system you described, with the exception that I'd not have 'Prefab Instances' (yet); I'd just have the prefab data hierarchy.

"I just want to make sure that what you want is inheritable properties on individual objects rather than inheritable properties on types of objects."  Confuses me a little?  Are you wondering if I want to setup a type inheritance chain where I override the default value that would be constructed?  I.e.:

Class Bar { float Health = 100.0f; }
Class Foo : Bar { float Heath = 200.0f; } // just kidding, redefine defaults when using this class

Because no, that's crazy and insane, and not very practical for designers / artists.  :)

Yes, I would end up utilizing DOM for the points you made, because I don't want to deal with writing all that code.

Question, which relates to my other question (about WPF wrappers), how would you go about integrating both of these adapters?  In this case, I'd want the WPF adapter to wrap around the InheritableDomNode adapter (which wraps around the DomNode).  (I'd really like to code gen each property to go through inheritance.)  I have the naive instinct to make them the same adapter, but feel like isn't really a good idea.

For the classes, I'm talking about:

// code genned from Julianne's awesome property work?
class FooWPF : ObservableDomNodeAdapter, INotifyPropertyChanged
{
        [ObservableDomProperty("name")]
        public string Name
        {
            get { return GetAttribute<string>(schema.namedType.nameAttribute); }
            set { DomNode.SetAttribute(schema.namedType.nameAttribute, value); }
        }
}

using InheritableProperty;

// lets say I code genned this.
class FooInheritable : DomNodeAdapter
{
       InheritableProperty m_name;
       public Name 
       {
              get { m_name.GetValue(DomNode); }
       }
}

And in my control, I'd just want to bind against SomeFooWithABase.Name; and whenever the base changes, I also get that update in the UI.

Basically, I want a containment pattern like this:  FooWPF contains and exposes FooInheritable,  FooInheritable contains and exposes DomNode.

Maybe I'm making things too complicated?  :(

Ron2

unread,
Dec 11, 2014, 4:40:25 PM12/11/14
to authoring-to...@googlegroups.com
Hey Jason,

OK, I think I understand what you're trying to do. By "types of objects", I was thinking of types of objects from the user's point of view, like Prefabs basically, and not C# types.

Since you're using WPF and the ObservableDomPropertyAttribute, it's probably possible to create an InheritableObservableDomNodeAdapter (what a mouthful!) that can use reflection (just like ObservableDomNodeAdapter) to look for attributes (e.g., InheritableObservableDomPropertyAttribute) and also provide a Base property. That might be over-engineering, but it might be worthwhile if you have many classes like FooWPF and FooInheritable.

You might be able to do the inheritance that you describe and make FooInheritable derive from FooWPF and make FooWPF's properties (e.g., Name, Health, etc.) virtual and override them. I think that will work as long as the Base FooInheritable doesn't change while the derived FooInheritable object is selected and edited by the user. If the Base were to change while a derived object is selected, then the INotifyPropertyChanged event won't fire.

(I'm not a WPF programmer, so, it's more likely than usual that I've missed something. :-)  )

I thought a little bit more about this InheritableProperty class. If it takes an AttirbuteInfo in the constructor, it might as well take the current DomNode, too. The constructor will need to be called from OnNodeSet() so that the DomNode property is available. Revised version:

    public class InheritableProperty
    {
        public InheritableProperty(DomNode ourNode, AttributeInfo attribute)
        {
            m_ourNode = ourNode;
            m_attribute = attribute;
        }

        public DomNode BaseNode { get; set; }

        public object Value
        {
            get
            {
                if (BaseNode != null && m_ourNode.IsAttributeDefault(m_attribute))
                    return BaseNode.GetAttribute(m_attribute);
                return m_ourNode.GetAttribute(m_attribute);
            }
            set
            {
                m_ourNode.SetAttribute(m_attribute, value);
            }
        }

        private readonly DomNode m_ourNode;
        private readonly AttributeInfo m_attribute;
    }

    public class FooWpf : ObservableDomNodeAdapter
    {
    }

    public class InheritableFoo : FooWpf
    {
        public int Height
        {
            get { return (int)m_height.Value; }
            set { m_height.Value = value;}
        }

        public int Health
        {
            get { return (int)m_health.Value; }
            set { m_health.Value = value; }
        }

        public int Wealth
        {
            get { return (int)m_wealth.Value; }
            set { m_wealth.Value = value; }
        }

        /// <summary>
        /// Gets or sets the base Foo object to be used for inheritable property values. If null,
        /// then this Foo object is used. Otherwise, the base Foo object is used for the value
        /// unless a particular property of ours (e.g., Height) has been changed.</summary>
        public InheritableFoo Base
        {
            get { return m_base; }
            set
            {
                m_base = value;

                // Be sure to set the BaseNode of all the properties that need it!
                m_height.BaseNode = value.As<DomNode>();
                m_health.BaseNode = value.As<DomNode>();
                m_wealth.BaseNode = value.As<DomNode>();
            }
        }

        protected override void OnNodeSet()
        {
            m_height = new InheritableProperty(DomNode, Schema.annotationType.heightAttribute);
            m_health = new InheritableProperty(DomNode, Schema.annotationType.healthAttribute);
            m_wealth = new InheritableProperty(DomNode, Schema.annotationType.wealthAttribute);
        }

        private InheritableFoo m_base;
        private InheritableProperty m_height;
        private InheritableProperty m_health;
        private InheritableProperty m_wealth;
    }

--Ron

Jason Batchkoff

unread,
Dec 11, 2014, 5:44:11 PM12/11/14
to authoring-to...@googlegroups.com
Ok, cool.  I'll play a little with that.  I'm curious if attributes on virtual properties inherit to overridden properties in C#...

Also, how would we automatically adapt our DomNode to the InheritableFoo?  I'm not super clear on what we'd have to set up at the start of the App?

I think we could have the InheritableFoo just subscribe to m_base.PropertyChanged() and just forward it, if our equivalent property IsAttributeDefault().  e.g.: 

    public class InheritableFoo : FooWpf
    {
        [ObservableDomProperty("height")]
        public int Height
        {
            get { return (int)m_height.Value; }
            set { m_height.Value = value;}
        }

        /// <summary>
        /// Gets or sets the base Foo object to be used for inheritable property values. If null,
        /// then this Foo object is used. Otherwise, the base Foo object is used for the value
        /// unless a particular property of ours (e.g., Height) has been changed.</summary>
        public InheritableFoo Base
        {
            get { return m_base; }
            set
            {
                m_base.PropertyChanged -= OnBasePropertyChanged;

                m_base = value;

                // Be sure to set the BaseNode of all the properties that need it!
                m_height.BaseNode = value.As<DomNode>();

                m_base.PropertyChanged += OnBasePropertyChanged;
            }
        }

        protected override void OnNodeSet()
        {
            m_height = new InheritableProperty(DomNode, Schema.annotationType.heightAttribute);
        }

        void OnBasePropertyChanged(object sender, PropertyChangedEvent e)
        {
            InheritableProperty basePropertyThatChanged = ;// find InheritableProperty with name 'e.PropertyName'
            if ( basePropertyThatChanged.IsDefaultValue() )
            {
                 // forward to all listeners that our property also changed, since we are defaulted to it.
                 RaisePropertyChanged( e.PropertyName );

Jason Batchkoff

unread,
Dec 11, 2014, 6:14:56 PM12/11/14
to authoring-to...@googlegroups.com
Thinking about it a little, I think I'm going to try reversing the inheritance pattern.  Seems FooInheritable is more general case than FooWPF.  Maybe that will be a horrible idea...?  I think I need to see how the Observable* classes/attrs actually work before we come up with a solution here.
Reply all
Reply to author
Forward
0 new messages