views:

257

answers:

4

I have a project with a large codebase (>200,000 lines of code) I maintain ("The core").

Currently, this core has a scripting engine that consists of hooks and a script manager class that calls all hooked functions (that registered via DLL) as they occur. To be quite honest I don't know how exactly it works, since the core is mostly undocumented and spans several years and a magnitude of developers (who are, of course, absent). An example of the current scripting engine is:

void OnMapLoad(uint32 MapID)
{
    if (MapID == 1234)
    {
        printf("Map 1234 has been loaded");
    }
}

void SetupOnMapLoad(ScriptMgr *mgr)
{
    mgr->register_hook(HOOK_ON_MAP_LOAD, (void*)&OnMapLoad);
}

A supplemental file named setup.cpp calls SetupOnMapLoad with the core's ScriptMgr.

This method is not what I'm looking for. To me, the perfect scripting engine would be one that will allow me to override core class methods. I want to be able to create classes that inherit from core classes and extend on them, like so:

// In the core:    
class Map
{
    uint32 m_mapid;
    void Load();
    //...
}

// In the script:
class ExtendedMap : Map
{
    void Load()
    {
        if (m_mapid == 1234)
            printf("Map 1234 has been loaded");

        Map::Load();
    }
}

And then I want every instance of Map in both the core and scripts to actually be an instance of ExtendedMap.

Is that possible? How?

+2  A: 

The inheritance is possible. I don't see a solution for replacing the instances of Map with instances of ExtendedMap.

Normally, you could do that if you had a factory class or function, that is always used to create a Map object, but this is a matter of existing (or inexistent) design.

The only solution I see is to search in the code for instantiations and try to replace them by hand. This is a risky one, because you might miss some of them, and it might be that some of the instantiations are not in the source code available to you (e.g. in that old DLL).

Later edit This method overriding also has a side effect in case of using it in a polymorphic way.

Example:

Map* pMyMap = new ExtendedMap; 

pMyMap->Load(); // This will call Map::Load, and not ExtendedMap::Load.
Cătălin Pitiș
I don't want the source to have `ExtendedMap` if the scripts do not provide such a class. The idea is to re-register certain (or all) classes after I extend them, in the DLL itself
Spidey
+2  A: 

This sounds like a textbook case for the "Decorator" design pattern.

Jim Lewis
Sounds close to what I'm looking for, but how do I cause the core to always use the decorator provided by scripts and fallback to the base class (or a decorator of it)? I want my core to always use `MapDecorator`, which _could_ be defined in an external DLL
Spidey
I think you'd have to do some refactoring of the core code, so that all creation of Map objects is done through a factory, which would expose some interface that your script hooks could use to control which flavor of decorated Map gets created. I like some of the ideas I've seen in the other answers; maybe one of them is a better match for your situation.
Jim Lewis
A: 

Although it's possible, it's quite dangerous: the system should be open for extension (i.e. hooks), but closed for change (i.e. overriding/redefining). When inheriting like that, you can't anticipate the behaviour your client code is going to show. As you see in your example, client code must remember to call the superclass' method, which it won't :)

An option would be to create a non-virtual interface: an abstract base class that has some template methods that call pure virtual functions. These must be defined by subclasses.

If you want no core Map's to be created, the script should give the core a factory to create Map descendants.

xtofl
A: 

If my experience with similar systems is applicable to your situation, there are several hooks registered. So basing a solution on the pattern abstract factory will not really work. Your system is near of the pattern observer, and that's what I'd use. You create one base class with all the possible hooks as virtual members (or several one with related hooks if the hooks are numerous). Instead of registering hooks one by one, you register one object, of a type descendant of the class with the needed override. The object can have state, and replace advantageously the void* user data fields that such callbacks system have commonly.

AProgrammer