Possible confusion between Dom "Extensions" and "Adapters"

41 views
Skip to first unread message

TwainJ

unread,
Jul 9, 2015, 8:51:40 PM7/9/15
to authoring-to...@googlegroups.com
I thought I had my head pretty well wrapped around the idea of DomNodeAdapters and Extensions and things, but I came across a usage that makes me think there's a gap in my understanding.

The simple question is: Are "Dom Adapters" (the DomNodeAdapter or a subclass), and "Dom Extensions" (a specific way of attaching the adapter) perfectly synonymous?  Here's what makes me wonder if they are not:

I have a circumstance where I need multiple instances of an adapter (derived from DomNodeAdapter) that refer to the same DOM Node. That is, they both have references to the same internal DomNode, but there are other data members that might be different in different contexts (If you need more info on why this usage came up, let me know. I'd be interested to know how it fits with intended ATF usage.)

Consider the following unit test, patterned after tests in the TestDomNodeAdapter class, which checks for the circumstances I need:

[Test]
public void TestMultipleAdapterInstances()
{
    DomNodeType type = new DomNodeType("type");
    
    // Use only one of the following two lines
    type.Define(new ExtensionInfo<VisibleAdapter>());
    type.AddAdapterCreator(new AdapterCreator<VisibleAdapter>());

    DomNode node = new DomNode(type);

    VisibleAdapter test1 = node.GetAdapter(typeof(VisibleAdapter)) as VisibleAdapter;
    VisibleAdapter test2 = node.GetAdapter(typeof(VisibleAdapter)) as VisibleAdapter;

    Assert.AreNotSame(test1, test2);

    var test3 = node.As<VisibleAdapter>();
    var test4 = node.As<VisibleAdapter>();

    Assert.AreNotSame(test3, test4);
    Assert.AreNotSame(test1, test3);

}

Per the comment, there are two ways I've found to attach the adapter to the DomNodeType. I originally used the first, "type.Define(...)", following SchemaLoader boilerplate in the docs. But it turns out that this uses an Adapter creator that always returns the same instance, so this test fails. I discovered the AdapterCreator<> class, which creates a new instance each time, and if I use just that second line, the test passes.

So I could just replace the extension code with the AdapterCreator, but I'm wondering what functionality is being lost by not having that ExtensionInfo defined on the type. I did some digging around the code, and it seems that the ExtensionInfo is just a metadata container for the adapter, but I'm not sure when that metadata comes into play. The wiki is a little thin on it.

Could you point me to a good sample that would clarify?

Ron2

unread,
Jul 14, 2015, 10:57:58 AM7/14/15
to authoring-to...@googlegroups.com, ja...@2jdevelopment.com
Hi Jason,

Sorry for the delayed response. I was on vacation and this is a pretty deep question!

From the DomNode's point of view, you could say that extensions are the objects that are permanently attached to that DomNode instance, by being stored in the DomNode's m_data array. These extensions can be DomNode attributes, DomNode children, and DomNodeAdapters. Here are the methods in DomNode that build m_data and retrieve extensions from it:

        public DomNode(DomNodeType nodeType, ChildInfo childInfo)
       
{
 
...
            m_data
= nodeType.FieldCount > 0 ? new object[nodeType.FieldCount] : EmptyArray<object>.Instance;


           
int extensionOffset = nodeType.FirstExtensionIndex;
           
int i = 0;
           
foreach (ExtensionInfo extensionInfo in nodeType.Extensions)
           
{
               
object extension = extensionInfo.Create(this);
                m_data
[i + extensionOffset] = extension;
                i
++;
           
}
       
}

       
public object GetExtension(ExtensionInfo extensionInfo)
       
{
           
int index = m_type.GetDataIndex(extensionInfo);
           
object extension = m_data[index];
           
return extension;
       
}





So I could just replace the extension code with the AdapterCreator, but I'm wondering what functionality is being lost by not having that ExtensionInfo defined on the type.

By using AdapterCreator:
1. There might be performance issues, since a DomNodeAdapter has to be created each time it's needed, whereas with ExtensionInfo, the DomNodeAdapter is created just once per DomNode and can be quickly accessed.
2. If you have custom data on your DomNodeAdapter that is not backed by the DomNode, then you won't be able to share that data between different callers just by having access to the DomNode (since you'll be getting a new object each time).
Note that two DomNodeAdapter objects are considered "equal" (the Equals and GetHashCode methods are overridden) if they are attached to the same DomNode. So, in your unit tests, Assert.AreEqual should pass.

In our sample apps, we use CustomTypeDescriptorNodeAdapter with AdapterCreator, like this:

// Enable metadata driven property editing for the DOM
DomNodeType.BaseOfAllTypes.AddAdapterCreator(new AdapterCreator<CustomTypeDescriptorNodeAdapter>());

But why?! I can't quickly see any particular reason why AdapterCreator was used instead of ExtensionInfo, like this:

DomNodeType.BaseOfAllTypes.Define(new ExtensionInfo<CustomTypeDescriptorNodeAdapter>());

CircuitEditor, for example, seems to be able to edit properties and otherwise run fine with ExtensionInfo.

What was the use case you ran into, where you needed a unique object returned by DomNode.As<T> and DomNode.GetAdapter?

--Ron


TwainJ

unread,
Jul 14, 2015, 6:22:27 PM7/14/15
to authoring-to...@googlegroups.com
Hi Ron, no worries about the delay, I hope you enjoyed your vacation time!

Thanks for the info. I hadn't caught the inclusion of the ExtensionInfo in the m_data array. That does help to clarify. If I understand correctly, in the case of the extension being a DomNodeAdapter, the very purpose of storing it in DomNode.m_data is for the convenience of accessibility, and to allow sharing of that data between callers, as you say. The wiki is pretty clear that DomNodeAdapters are the most common case of an extension. And that is certainly the case in my scenario, so I think that answers my question for the present.

For the sake of understanding, though, I'm curious about the cases you mentioned where the extension could be DomNode attributes or DomNode children. I'm not sure what that would look like. Aren't all DomNode attributes and DomNode child relationships defined in the DomNode type classes (generated by the DomGen tool, or manually, as in the "NoXml" sample)? So the DomNode should have all of those things defined in it's m_data array already. Perhaps I'm misunderstanding what you meant.

I'll try to write up a simplified version of my use case a little later when I have some time on my hands. I'd be interested to hear your take on it.

~JJ

Ron2

unread,
Jul 14, 2015, 7:07:51 PM7/14/15
to authoring-to...@googlegroups.com, ja...@2jdevelopment.com
Yes, the DomNode attribute and child relationships (AttributeInfo and ChildInfo) are defined on the DomNodeType, but the actual attribute objects (e.g., a string or boxed int or array of floats or whatever) are stored in DomNode.m_data and accessed with an index that is on the AttributeInfo. Likewise, a child DomNode or child list of DomNodes are stored in m_data, too, and accessed with an index that is on the ChildInfo. AttributeInfo and ChildInfo both derive from FieldMetaData which has the index that is used to index into m_data.

Good questions! I'd love to hear about your use case for AdapterCreator.

--Ron

TwainJ

unread,
Aug 17, 2015, 3:19:26 PM8/17/15
to Authoring Tools Framework, ja...@2jdevelopment.com
Hi Ron, Sorry, it's my turn to delay from being on a vacation. It's taken a little time to get my head back in the game.

The basic motivation for something like AdapterCreator is to have multiple related documents (part of the same project), and references between them

So, for instance, say I have a 2D tile-based RPG style game, reminiscent of the old console RPGs. I might have a document that represents a tile set that can be used for certain types of scenery - a town, say. And then another document that contains data (layout, existence of interactive entities, etc) about a specific town scene, with a reference to that tileset. Consider if the placement of the tiles indicated certain shading effects that might be necessary for each tile.  So, the DomNodeAdapter for the instance of the tile in the scene would delegate the drawing of tiles to the adapter for the tileset (and any child adapters for individual tiles). But the tileset needs to query placement information from the scene so that it knows how to shade the tile.

I guess it is a bit of a contrived example, and this might not be the best way to go about this in the example. But it is a simpler to explain than the document relationships that brought up the question.  In general, it is when I have a project with multiple documents, where one type of document describes a set of rules (like the tileset - how to draw specific tiles), and other documents describe data those rules act on (the scene). The DomNode/document for the rule wouldn't contain links to the data that uses it - many documents might use it. But an adapter for the rule would need to have a reference to the client Data Node so that it can query the data it arbitrates in a given instance. So I would need a separate Rule DomNodeAdapter for each Data document that uses the rule.

As I've been thinking through all this, a couple ideas have come to mind how this might be managed without the need for multiple adapters on a rule node, but I'll have to experiment with them and see if they make sense.  I'd love to hear your perspective, though, if there is a standard approach others have found to situations like this.

~JJ

Ron2

unread,
Aug 17, 2015, 7:19:58 PM8/17/15
to Authoring Tools Framework, ja...@2jdevelopment.com
Hi Jason,

Thanks for explaining what your use-case is like.

You remind me of a pattern that we use in several places, where a high-level long-lived object (usually an Editor) knows how to render or edit the numerous small objects (tiles in your example) within a document. What's somewhat unusual about your case is that the set of types of objects, or palette, is dynamic and document-based, but that can still work fine -- it's like circuit editor templates. For example, you could have a TilePaletteDoc (which has a collection of types of tiles) that is a DomNodeAdapter on the root of a DomNode tree that was loaded as a document by your Editor. Your Editor (which knows how to load / save documents and do editing operations on them) could take a LevelDoc (a layout of tile references and other objects and properties that describe a game level) and the TilePaletteDoc, and render the Tiles. So, you wouldn't need a particular Tile to be adaptable to various TileRenderingRules at run-time, because instead each Tile would have a TileType reference or identifier, and the TileTypes are located in the TilePaletteDoc, which the Editor has access to.

That's what comes to mind. I hope that helps.

--Ron

TwainJ

unread,
Sep 7, 2015, 3:25:26 PM9/7/15
to Authoring Tools Framework, ja...@2jdevelopment.com
Hi Ron, I just wanted to follow up, and thank you for that response. It is an interesting approach, basically making the Editor a sort of central authority around those rules, and the data they apply to. It makes sense, as the Editor is already the one place where all that data has to converge.

In case you or anyone else that reads this thread is wondering, the way I ended up solving it, is also going back to the standard lifetime for the DomNodeAdapters (finding the existing adapter, with the Type.Define({ExtensionInfo}) creation pattern, rather than the AdapterCreator). Then instead of giving the Rule adapter a reference to the data it needs to arbitrate those rules (A separate Tile Palette with a reference to the Scene it operates on), the Data is passed in to the method that the Rule arbitrates, in the moment it is needed. So in my example, the scene might call palette.RenderTile(scene, x, y). Come to think of it, the client of the palette could even be the editor, as in your example. The two patterns are not exclusive.

Anyway, thanks for talking me through all this, it has helped my understanding of the DomNodes and their adapters immensely!

~Jason
Reply all
Reply to author
Forward
0 new messages