Inheritance baggage

A couple posts ago, I mentioned that I’ve been working with code generation lately.  This is for a part of the TURBU project.  An RPG relies pretty heavily on scripting, and RPG Maker, the system I created TURBU to replace, has a fairly extensive, if limited, scripting system.  The limitations were one of the things that made me say “I could do better than this,” in fact:  No functions, no local variables, callable procedures exist but parameters don’t, so any “passing” has to be done in global variables, only two data types: integer and boolean, no event handlers, minimal looping support, etc.

The upside of all this, though, is a very simple scripting system that doesn’t look much like a programming language, with a simple interface that almost anyone can pick up.  I wanted to keep that simplicity as much as possible, while adding the full flexibility and power of a real scripting language.  So I dreamed up EventBuilder, a set of objects which represent a high-level scripting interface and can also express themselves as PascalScript code.

I needed some way to create EventBuilder objects that could form a hierarchical tree that can represent blocks of code.  They needed to be easily serializable to some human-readable format so people can copy and paste blocks of EventBuilder script in order to share scripts, ask for help with debugging, etc.  And it needed to be ready quickly, since I want to be able to present as much of this as possible at Delphi Live! in August.

So is there any pre-existing system that supports hierarchical trees of objects and easy serialization to a simple text-based format?  The answer should be obvious to any experienced Delphi user:  descend from TComponent and use its built-in serialization to “DFM format.”  I tried that and, once I’d figured out how to handle a few quirks related to object ownership, it worked great!  All the infrastructure was there for me, tested and tried and proven over the last 15 years, and I could focus on the actual Event Builder logic.  It’s taken me about a month to get the system to a workable state, and now it’s more or less all ready.

Then I tried running a very, very large RPG Maker project through my project importer, and it took a long time on converting the global script block.  That’s sort of to be expected, since there are almost 2000 event scripts in there, but even so it felt like it was taking far too long for the amount of work involved.  I looked at my code and couldn’t find any obvious issues, so I ran it through Sampling Profiler.

It’s a good thing I did, too.  It found a very clear bottleneck in a place I’d have never thought to look.  Apparently I was spending 77% of my time in TComponent.Notification.  And why would I have never thought to look there?  Because I’ve never heard of it!  But apparently every time I added a component, it would recursively call this on the entire subtree, turning what ought to have been a O(n) conversion into O(n^2).

With a bit of research, it turns out that TComponent.Notification is for dealing with linked components.  For example, when you link a TDataset to a TDatasource, it needs a notification mechanism so it can clean up references if you free one of them.  Since EventBuilder doesn’t use linked components, I didn’t really need this functionality.  Good thing TComponent.Notification is virtual!  I overrode it with a blank method, and suddenly the conversion time dropped from about 12 seconds to about 3 seconds, and everything’s running smoothly again.

Moral of the story?  Be careful that you understand what you’re inheriting from, otherwise you might end up with killer kangaroos or other unwanted features.

6 Comments

  1. tondrej says:

    For serialization (without the TComponent overhead) you could start with TPersistent.

  2. François says:

    Notification is one of the reason why you don’t want to Create a temp Component (Form, Dialog,…) with an Owner when you know you’ll free it 10 lines of code below. You use nil and the usual try..finally to Free (Release) it.
    On a big Application with lots of very populated Forms, you avoid a zillion Notification messages flying around.

  3. Mason Wheeler says:

    tondrej: True, but the code for creating hierarchical trees of objects is introduced at the TComponent level.

  4. tondrej says:

    Yes, you need at least one TComponent descendant as a root.

  5. Mason Wheeler says:

    Actually, they all have to be TComponent descendants in order for the tree structure to work right, because of the way the serializer works. For example, can you find any way to create a TComponent root object that contains an arbitrary number of TStrings descendants? (Just to name one well-known class that descends from TPersistent but not TComponent.)

    Also, as long as the root is going to be a TComponent, you may as well make them all TComponents so you can declare the GetScript method (and a handful of other bits of common functionality) on the root class and all the descendant objects inherit or override it.

  6. tondrej says:

    For example, TCollection, TCollectionItem and their descendants in the VCL.