Defining a Custom Method: Difference between revisions
Eric Lengyel (talk | contribs) (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...") |
Eric Lengyel (talk | contribs) 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. | ||
< | <syntaxhighlight lang="c++"> | ||
enum | enum | ||
{ | { | ||
kMethodChangeLightColor = 'litc' | kMethodChangeLightColor = 'litc' | ||
}; | }; | ||
</ | </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—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—these are discussed below. | ||
< | <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; | ||
}; | }; | ||
</ | </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. | ||
< | <syntaxhighlight lang="c++"> | ||
ChangeLightColorMethod::ChangeLightColorMethod() : Method(kMethodChangeLightColor) | ChangeLightColorMethod::ChangeLightColorMethod() : Method(kMethodChangeLightColor) | ||
{ | { | ||
Line 75: | Line 75: | ||
{ | { | ||
} | } | ||
</ | </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. | ||
< | <syntaxhighlight lang="c++"> | ||
ChangeLightColorMethod::ChangeLightColorMethod(const ChangeLightColorMethod& changeLightColorMethod) : Method(changeLightColorMethod) | ChangeLightColorMethod::ChangeLightColorMethod(const ChangeLightColorMethod& changeLightColorMethod) : Method(changeLightColorMethod) | ||
{ | { | ||
lightColor = changeLightColorMethod.lightColor; | lightColor = changeLightColorMethod.lightColor; | ||
} | } | ||
</ | </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. | ||
< | <syntaxhighlight lang="c++"> | ||
Method *ChangeLightColorMethod::Replicate(void) const | Method *ChangeLightColorMethod::Replicate(void) const | ||
{ | { | ||
return (new ChangeLightColorMethod(*this)); | return (new ChangeLightColorMethod(*this)); | ||
} | } | ||
</ | </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. | ||
< | <syntaxhighlight lang="c++"> | ||
// Define the method registration. | // Define the method registration. | ||
MethodReg<ChangeLightColorMethod> changeLightColorRegistration; | MethodReg<ChangeLightColorMethod> changeLightColorRegistration; | ||
</ | </syntaxhighlight> | ||
The registration object is initialized with the following code. | The registration object is initialized with the following code. | ||
< | <syntaxhighlight lang="c++"> | ||
changeLightColorRegistration(kMethodChangeLightColor, "Change Light Color"); | changeLightColorRegistration(kMethodChangeLightColor, "Change Light Color"); | ||
</ | </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. | ||
< | <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; | ||
} | } | ||
</ | </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. | ||
< | <syntaxhighlight lang="c++"> | ||
void ChangeLightColorMethod::BuildSettingList(List<Setting> *settingList) const | void ChangeLightColorMethod::BuildSettingList(List<Setting> *settingList) const | ||
{ | { | ||
Line 158: | Line 158: | ||
} | } | ||
} | } | ||
</ | </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—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—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. | ||
< | <syntaxhighlight lang="c++"> | ||
void ChangeLightColorMethod::Execute(const ScriptState *state) | void ChangeLightColorMethod::Execute(const ScriptState *state) | ||
{ | { | ||
Line 188: | Line 188: | ||
HandleCompletion(); | HandleCompletion(); | ||
} | } | ||
</ | </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.