Skip to content

Chapter 4: Data Driven Programming

kytoo edited this page Jan 31, 2019 · 3 revisions

Data-driven development can be likened to test-driven development

In test-driven development, the program is designed around the test cases. Whenever adding something new to our program we first design the new test case, after that we implement new code to pass these new tests. Data-driven development is similar in that we design our code's data first, and then implement our code around our pre-planned data.

In traditional game programming, the workflow is:

Analyzing the requirements -> design a conceptual blueprint -> developer designs pseudo code from blueprint -> developer implements actual code -> release version

In NoahGameFrame, the workflow is:

Analyze the requirements (while using excel to generate the data structure automatically) -> design business interface -> coding (just implement the needs) -> release version

It's clear to know from the workflow that we can generate the configuration/data struct automatically when the developer analyzes the requirements with the designer, it's the way to design a logic class.

After we have the logic class struct, then we can add an observer for these classes, below are the examples:

class NFIProperty
{
    int64 GetInt();
    float GetFloat();
    string GetString();
    GUID GetObject();

    void SetInt(Int64 value);
    void SetFloat(float value);
    void SetString(string value);
    void SetObject(GUIDvalue);

    //adding a call back handler (observer)
    void RegisterCallback(PROPERTY_EVENT_FUNCTOR_PTR cb);

    //call all handler point when event happended
    int OnEventHandler(NFIDataList::TData oldVar, NFIDataList::TData newVar);

    //the handler list
    vector<PROPERTY_EVENT_FUNCTOR_PTR> mtPropertyCallback;
};

First of all, we would register a handler into the property:

void NFCProperty::RegisterCallback(const PROPERTY_EVENT_FUNCTOR_PTR& cb)
{
    mtPropertyCallback.push_back(cb);
}

Then, when we change the value of this property, it will trigger the event handler automatically.

bool NFCProperty::SetInt(const NFINT64 value)
{
    if (eType != TDATA_INT)
    {
        return false;
    }

    if (value == GetInt())
    {
        return false;
    }

    NFCDataList::TData oldValue;
    oldValue = GetInt();

    mxData->SetInt(value);

    //trigger
    OnEventHandler(oldValue, value);

    return true;
}

Finally the trigger working:

int NFCProperty::OnEventHandler(const NFIDataList::TData& oldVar, const NFIDataList::TData& newVar)
{
    if (mtPropertyCallback.size() <= 0)
    {
        return 0;
    }

    TPROPERTYCALLBACKEX::iterator it = mtPropertyCallback.begin();
    TPROPERTYCALLBACKEX::iterator end = mtPropertyCallback.end();
    for (it; it != end; ++it)
    {
        
        PROPERTY_EVENT_FUNCTOR_PTR& pFunPtr = *it;
        PROPERTY_EVENT_FUNCTOR* pFunc = pFunPtr.get();

        pFunc->operator()(mSelf, msPropertyName, oldVar, newVar);
    }

    return 0;
}

Yes, it's an observer pattern!

The class NFDataList::TData it is the same mean as the object of java/c#.

By now we can know that when we change the data the observer will call the callback function which we registered! So we can monitor the value of the props and add a logic function. For example, if we want to add some items for players when their level is changed, we just need to register a function as the callback function of the property named "Level", then we can add items for those players. It's easy to divide a big function into several small functions to reduce complexity.

The workflow of data-driven as below: