Defining a Custom Method: Difference between revisions

From C4 Engine Wiki
Jump to navigation Jump to search
(Created page with "In the C4 Engine, a ''method'' refers to an individual action that can appear in a script. There are many types of methods built into the engine, and an application can define its own custom methods by implementing new subclasses of the [http://c4engine.com/docs/Controller/Method.html Method] class. == Defining a Method Subclass == In this article, we will use the example of a method called <code>ChangeLightColorMethod</code> that simply changes the color of a light so...")
 
No edit summary
 
Line 7: Line 7:
A custom method subclass needs to have a unique type identifier that is registered with the engine so that the [[Script Editor]] knows about it. The type identifier is a 32-bit number normally represented by a four-character string as in the following code.
A custom method subclass needs to have a unique type identifier that is registered with the engine so that the [[Script Editor]] knows about it. The type identifier is a 32-bit number normally represented by a four-character string as in the following code.


<source lang="c++">
<syntaxhighlight lang="c++">
enum
enum
{
{
     kMethodChangeLightColor = 'litc'
     kMethodChangeLightColor = 'litc'
};
};
</source>
</syntaxhighlight>


(Type identifiers consisting of only uppercase letters and numbers are reserved for use by the engine. Anything else is okay for an application to use.)
(Type identifiers consisting of only uppercase letters and numbers are reserved for use by the engine. Anything else is okay for an application to use.)
Line 18: Line 18:
Next, the method subclass needs to be defined. We declare the <code>ChangeLightColorMethod</code> class to be a subclass of the <code>Method</code> class as follows, and we include a single data member to hold the color value that will be assigned to a light source when the method executes. Several member functions are included to handle serialization and user interface&mdash;these are discussed below.
Next, the method subclass needs to be defined. We declare the <code>ChangeLightColorMethod</code> class to be a subclass of the <code>Method</code> class as follows, and we include a single data member to hold the color value that will be assigned to a light source when the method executes. Several member functions are included to handle serialization and user interface&mdash;these are discussed below.


<source lang="c++">
<syntaxhighlight lang="c++">
class ChangeLightColorMethod : public Method
class ChangeLightColorMethod : public Method
{
{
Line 56: Line 56:
         void ExecuteMethod(const ScriptState *state) override;
         void ExecuteMethod(const ScriptState *state) override;
};
};
</source>
</syntaxhighlight>


The constructor and destructor for this example would typically be implemented as follows.
The constructor and destructor for this example would typically be implemented as follows.


<source lang="c++">
<syntaxhighlight lang="c++">
ChangeLightColorMethod::ChangeLightColorMethod() : Method(kMethodChangeLightColor)
ChangeLightColorMethod::ChangeLightColorMethod() : Method(kMethodChangeLightColor)
{
{
Line 75: Line 75:
{
{
}
}
</source>
</syntaxhighlight>


Notice that the method's type <code>kMethodChangeLightColor</code> is passed to the base class constructor.
Notice that the method's type <code>kMethodChangeLightColor</code> is passed to the base class constructor.
Line 81: Line 81:
The copy constructor and the <code>Replicate()</code> function must be included for all methods. For this example, the copy constructor would be implemented as follows.
The copy constructor and the <code>Replicate()</code> function must be included for all methods. For this example, the copy constructor would be implemented as follows.


<source lang="c++">
<syntaxhighlight lang="c++">
ChangeLightColorMethod::ChangeLightColorMethod(const ChangeLightColorMethod& changeLightColorMethod) : Method(changeLightColorMethod)
ChangeLightColorMethod::ChangeLightColorMethod(const ChangeLightColorMethod& changeLightColorMethod) : Method(changeLightColorMethod)
{
{
     lightColor = changeLightColorMethod.lightColor;
     lightColor = changeLightColorMethod.lightColor;
}
}
</source>
</syntaxhighlight>


It's important to observe that the reference to the copied <code>ChangeLightColorMethod</code> object is passed to the base class constructor this time instead of the method's type identifier.
It's important to observe that the reference to the copied <code>ChangeLightColorMethod</code> object is passed to the base class constructor this time instead of the method's type identifier.
Line 92: Line 92:
The <code>Replicate()</code> function simply constructs a new instance of the method using the copy constructor. This should always be implemented as follows.
The <code>Replicate()</code> function simply constructs a new instance of the method using the copy constructor. This should always be implemented as follows.


<source lang="c++">
<syntaxhighlight lang="c++">
Method *ChangeLightColorMethod::Replicate(void) const
Method *ChangeLightColorMethod::Replicate(void) const
{
{
     return (new ChangeLightColorMethod(*this));
     return (new ChangeLightColorMethod(*this));
}
}
</source>
</syntaxhighlight>


== Method Registration ==
== Method Registration ==
Line 103: Line 103:
A custom method type must be registered with the engine in order to be recognized by the [[Script Editor]]. This is accomplished by creating a <code>MethodReg</code> object. The method registration contains information about the method type and its name, and it's mere existence registers the method type that it represents. A method registration for the <code>ChangeLightColorMethod</code> class would normally look like the following.
A custom method type must be registered with the engine in order to be recognized by the [[Script Editor]]. This is accomplished by creating a <code>MethodReg</code> object. The method registration contains information about the method type and its name, and it's mere existence registers the method type that it represents. A method registration for the <code>ChangeLightColorMethod</code> class would normally look like the following.


<source lang="c++">
<syntaxhighlight lang="c++">
// Define the method registration.
// Define the method registration.
MethodReg<ChangeLightColorMethod> changeLightColorRegistration;
MethodReg<ChangeLightColorMethod> changeLightColorRegistration;
</source>
</syntaxhighlight>


The registration object is initialized with the following code.
The registration object is initialized with the following code.


<source lang="c++">
<syntaxhighlight lang="c++">
changeLightColorRegistration(kMethodChangeLightColor, "Change Light Color");
changeLightColorRegistration(kMethodChangeLightColor, "Change Light Color");
</source>
</syntaxhighlight>


There is an optional third parameter that can specify flags for the method, and these are described in the documentation for the [http://c4engine.com/docs/Controller/MethodReg.html MethodReg] class.
There is an optional third parameter that can specify flags for the method, and these are described in the documentation for the [http://c4engine.com/docs/Controller/MethodReg.html MethodReg] class.
Line 120: Line 120:
A custom method must implement the <code>Pack()</code> and <code>Unpack()</code> functions so that its data can be written to a file and later restored. (These functions override the virtual functions in the [http://c4engine.com/docs/ResourceMgr/Packable.html <code>Packable</code>] class.) Each of these functions needs to first call its counterpart in the <code>Method</code> base class. For the <code>ChangeLightColorMethod</code> example, these functions would typically be implemented as follows.
A custom method must implement the <code>Pack()</code> and <code>Unpack()</code> functions so that its data can be written to a file and later restored. (These functions override the virtual functions in the [http://c4engine.com/docs/ResourceMgr/Packable.html <code>Packable</code>] class.) Each of these functions needs to first call its counterpart in the <code>Method</code> base class. For the <code>ChangeLightColorMethod</code> example, these functions would typically be implemented as follows.


<source lang="c++">
<syntaxhighlight lang="c++">
void ChangeLightColorMethod::Pack(Packer& data, uint32 packFlags) const
void ChangeLightColorMethod::Pack(Packer& data, uint32 packFlags) const
{
{
Line 136: Line 136:
     data >> lightColor;
     data >> lightColor;
}
}
</source>
</syntaxhighlight>


== User Interface ==
== User Interface ==
Line 142: Line 142:
The <code>Method</code> class is a subclass of the [http://c4engine.com/docs/InterfaceMgr/Configurable.html <code>Configurable</code>] class, which means it can expose a user interface that appears in the [[Script Editor]]. The <code>Method</code> object is queried by the Script Editor for its configurable settings when the user double-clicks on the method to open its Method Info window. The <code>BuildSettingList()</code> function is called when the editor needs all of the configurable settings to be built, and the <code>CommitSetting()</code> function is called for each setting when the user confirms the new configuration. For the <code>ChangeLightColorMethod</code> example, there is one setting representing the light color stored in the method object. The user interface to change this color would be implemented as follows.
The <code>Method</code> class is a subclass of the [http://c4engine.com/docs/InterfaceMgr/Configurable.html <code>Configurable</code>] class, which means it can expose a user interface that appears in the [[Script Editor]]. The <code>Method</code> object is queried by the Script Editor for its configurable settings when the user double-clicks on the method to open its Method Info window. The <code>BuildSettingList()</code> function is called when the editor needs all of the configurable settings to be built, and the <code>CommitSetting()</code> function is called for each setting when the user confirms the new configuration. For the <code>ChangeLightColorMethod</code> example, there is one setting representing the light color stored in the method object. The user interface to change this color would be implemented as follows.


<source lang="c++">
<syntaxhighlight lang="c++">
void ChangeLightColorMethod::BuildSettingList(List<Setting> *settingList) const
void ChangeLightColorMethod::BuildSettingList(List<Setting> *settingList) const
{
{
Line 158: Line 158:
     }
     }
}
}
</source>
</syntaxhighlight>


The first parameter passed to the <code>ColorSetting</code> constructor (<code>'colr'</code>) is just an identifier that the property uses to keep track of which setting is which&mdash;it can be anything you want. The second parameter is the color that will initially be shown to the user. The third parameter is the title of the setting that will be displayed next to the color box in the settings list. The last parameter is the title that will be used for the color picker dialog when the user clicks on the color box.
The first parameter passed to the <code>ColorSetting</code> constructor (<code>'colr'</code>) is just an identifier that the property uses to keep track of which setting is which&mdash;it can be anything you want. The second parameter is the color that will initially be shown to the user. The third parameter is the title of the setting that will be displayed next to the color box in the settings list. The last parameter is the title that will be used for the color picker dialog when the user clicks on the color box.
Line 168: Line 168:
The <code>ExecuteMethod()</code> function for the <code>ChangeLightColorMethod</code> class could be implemented as follows.
The <code>ExecuteMethod()</code> function for the <code>ChangeLightColorMethod</code> class could be implemented as follows.


<source lang="c++">
<syntaxhighlight lang="c++">
void ChangeLightColorMethod::Execute(const ScriptState *state)
void ChangeLightColorMethod::Execute(const ScriptState *state)
{
{
Line 188: Line 188:
     HandleCompletion();
     HandleCompletion();
}
}
</source>
</syntaxhighlight>


The call to the <code>GetTargetNode()</code> function retrieves the node that the user has selected as the target of the method in the [[Script Editor]]. It's possible that this target hasn't been selected or that the user linked it to a node that was not a light source, so the above code performs some checks before it carries out its task. Some custom methods do not operate on a target node, and these methods should specify the <code>kMethodNoTarget</code> flag in the method registration so that the user cannot select a target node in the Script Editor.
The call to the <code>GetTargetNode()</code> function retrieves the node that the user has selected as the target of the method in the [[Script Editor]]. It's possible that this target hasn't been selected or that the user linked it to a node that was not a light source, so the above code performs some checks before it carries out its task. Some custom methods do not operate on a target node, and these methods should specify the <code>kMethodNoTarget</code> flag in the method registration so that the user cannot select a target node in the Script Editor.

Latest revision as of 21:38, 12 September 2023

In the C4 Engine, a method refers to an individual action that can appear in a script. There are many types of methods built into the engine, and an application can define its own custom methods by implementing new subclasses of the Method class.

Defining a Method Subclass

In this article, we will use the example of a method called ChangeLightColorMethod that simply changes the color of a light source. Note that this particular functionality already exists as part of the more general built-in method Change Settings, so the example here is for illustrative purposes only.

A custom method subclass needs to have a unique type identifier that is registered with the engine so that the Script Editor knows about it. The type identifier is a 32-bit number normally represented by a four-character string as in the following code.

enum
{
    kMethodChangeLightColor = 'litc'
};

(Type identifiers consisting of only uppercase letters and numbers are reserved for use by the engine. Anything else is okay for an application to use.)

Next, the method subclass needs to be defined. We declare the ChangeLightColorMethod class to be a subclass of the Method class as follows, and we include a single data member to hold the color value that will be assigned to a light source when the method executes. Several member functions are included to handle serialization and user interface—these are discussed below.

class ChangeLightColorMethod : public Method
{
   private:

        ColorRGB    lightColor;

        ChangeLightColorMethod(const ChangeLightColorMethod& changeLightColorMethod);

        Method *Replicate(void) const;

   public:

        ChangeLightColorMethod();
        ChangeLightColorMethod(const ColorRGB& color);
        ~ChangeLightColorMethod();

        const ColorRGB& GetLightColor(void) const
        {
            return (lightColor);
        }

        void SetLightColor(const ColorRGB& color)
        {
            lightColor = color;
        }

        // Serialization functions.
        void Pack(Packer& data, uint32 packFlags) const override;
        void Unpack(Unpacker& data, uint32 unpackFlags) override;

        // User interface functions.
        void BuildSettingList(List<Setting> *settingList) const override;
        void CommitSetting(const Setting *setting) override;

        // This function is called when the method is executed in a script.
        void ExecuteMethod(const ScriptState *state) override;
};

The constructor and destructor for this example would typically be implemented as follows.

ChangeLightColorMethod::ChangeLightColorMethod() : Method(kMethodChangeLightColor)
{
    // Set a default value.
    lightColor.Set(1.0F, 1.0F, 1.0F);
}

ChangeLightColorMethod::ChangeLightColorMethod(const ColorRGB& color) : Method(kMethodChangeLightColor)
{
    lightColor = color;
}

ChangeLightColorMethod::~ChangeLightColorMethod()
{
}

Notice that the method's type kMethodChangeLightColor is passed to the base class constructor.

The copy constructor and the Replicate() function must be included for all methods. For this example, the copy constructor would be implemented as follows.

ChangeLightColorMethod::ChangeLightColorMethod(const ChangeLightColorMethod& changeLightColorMethod) : Method(changeLightColorMethod)
{
    lightColor = changeLightColorMethod.lightColor;
}

It's important to observe that the reference to the copied ChangeLightColorMethod object is passed to the base class constructor this time instead of the method's type identifier.

The Replicate() function simply constructs a new instance of the method using the copy constructor. This should always be implemented as follows.

Method *ChangeLightColorMethod::Replicate(void) const
{
    return (new ChangeLightColorMethod(*this));
}

Method Registration

A custom method type must be registered with the engine in order to be recognized by the Script Editor. This is accomplished by creating a MethodReg object. The method registration contains information about the method type and its name, and it's mere existence registers the method type that it represents. A method registration for the ChangeLightColorMethod class would normally look like the following.

// Define the method registration.
MethodReg<ChangeLightColorMethod> changeLightColorRegistration;

The registration object is initialized with the following code.

changeLightColorRegistration(kMethodChangeLightColor, "Change Light Color");

There is an optional third parameter that can specify flags for the method, and these are described in the documentation for the MethodReg class.

Serialization

A custom method must implement the Pack() and Unpack() functions so that its data can be written to a file and later restored. (These functions override the virtual functions in the Packable class.) Each of these functions needs to first call its counterpart in the Method base class. For the ChangeLightColorMethod example, these functions would typically be implemented as follows.

void ChangeLightColorMethod::Pack(Packer& data, uint32 packFlags) const
{
    Method::Pack(data, packFlags);

    // Write the ColorRGB object.
    data << lightColor;
}

void ChangeLightColorMethod::Unpack(Unpacker& data, uint32 unpackFlags)
{
    Method::Unpack(data, unpackFlags);

    // Read the ColorRGB object.
    data >> lightColor;
}

User Interface

The Method class is a subclass of the Configurable class, which means it can expose a user interface that appears in the Script Editor. The Method object is queried by the Script Editor for its configurable settings when the user double-clicks on the method to open its Method Info window. The BuildSettingList() function is called when the editor needs all of the configurable settings to be built, and the CommitSetting() function is called for each setting when the user confirms the new configuration. For the ChangeLightColorMethod example, there is one setting representing the light color stored in the method object. The user interface to change this color would be implemented as follows.

void ChangeLightColorMethod::BuildSettingList(List<Setting> *settingList) const
{
    // There is one setting, and it's a color picker.
    settingList->AppendListElement(new ColorSetting('colr', lightColor, "Light color", "New Light Color"));
}

void ChangeLightColorMethod::CommitSetting(const Setting *setting)
{
    // Are we setting the light color?
    if (setting->GetSettingIdentifier() == 'colr')
    {
        // Yes, grab the RGB color from the setting.
        lightColor = static_cast<const ColorSetting *>(setting)->GetColor().GetColorRGB();
    }
}

The first parameter passed to the ColorSetting constructor ('colr') is just an identifier that the property uses to keep track of which setting is which—it can be anything you want. The second parameter is the color that will initially be shown to the user. The third parameter is the title of the setting that will be displayed next to the color box in the settings list. The last parameter is the title that will be used for the color picker dialog when the user clicks on the color box.

Method Execution

When a script is running and it's time to execute your custom method, the script controller calls the method's ExecuteMethod() function. A method can do anything it wants when it executes as long as it doesn't cause itself to be deleted. When a method finishes performing its actions, it needs to indicate that it has completed by calling the HandleCompletion() function of its Completable base class. A method may finish inside the call to its ExecuteMethod() function, or it may finish sometime later after more time-consuming operations have completed.

The ExecuteMethod() function for the ChangeLightColorMethod class could be implemented as follows.

void ChangeLightColorMethod::Execute(const ScriptState *state)
{
    // Get the target node for this method and
    // make sure it's actually a light source.
    Node *node = GetTargetNode(state);
    if ((node) && (node->GetNodeType() == kNodeLight))
    {
        LightObject *object = static_cast<Light *>(node)->GetObject();

        // Set the new light color.
        object->SetLightColor(lightColor);

        // Mark the object as modified.
        object->SetModifiedFlag();
    }

    // Indicate that we are finished.
    HandleCompletion();
}

The call to the GetTargetNode() function retrieves the node that the user has selected as the target of the method in the Script Editor. It's possible that this target hasn't been selected or that the user linked it to a node that was not a light source, so the above code performs some checks before it carries out its task. Some custom methods do not operate on a target node, and these methods should specify the kMethodNoTarget flag in the method registration so that the user cannot select a target node in the Script Editor.

The SetModifiedFlag() function should be called whenever an object is modified so that the engine knows to write the object data when a game is saved. If this flag is not set, then the original object data would be reloaded when a saved game is resumed.

Method Output Values

A method may produce an output value that can be stored in a script variable. A method may also generate an independent boolean result that is used for conditional execution of subsequent methods in a script. A method can specify its output value by calling the SetOutputValue() function. When a method outputs a value, it should specify the kMethodOutputValue flag in its method registration so that the Script Editor allows the user to assign an output variable to the method.

If a method calls the SetOutputValue() function, then the boolean result for the method is automatically set according to the output value. A method can set the boolean result without specifying an output value, or it can override the output value, by calling the SetMethodResult() function.

See Also