Initial commit
This commit is contained in:
commit
4624668be3
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
book
|
6
book.toml
Normal file
6
book.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[book]
|
||||||
|
authors = ["Anton Tarasenko"]
|
||||||
|
language = "en"
|
||||||
|
multilingual = false
|
||||||
|
src = "src"
|
||||||
|
title = "Lazy cookbook"
|
17
src/SUMMARY.md
Normal file
17
src/SUMMARY.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Summary
|
||||||
|
|
||||||
|
- [Collections recipes](./collections/index.md)
|
||||||
|
- [Storing built-in values](./collections/storing_builtin.md)
|
||||||
|
- [Using iterators](./collections/iterators.md)
|
||||||
|
- [Using JSON data](./collections/using_json.md)
|
||||||
|
- [Acedia events system](./events/index.md)
|
||||||
|
- [Connecting to signal](./events/connecting.md)
|
||||||
|
- [Disconnecting from signal](./events/disconnecting.md)
|
||||||
|
- [Custom signals](./events/custom.md)
|
||||||
|
- [Feature recipes](./feature/index.md)
|
||||||
|
- [Creating a `Feature`](./feature/creating_feature.md)
|
||||||
|
- [Packaging a `Feature`](./feature/packaging_feature.md)
|
||||||
|
- [AcediaObject recipes](./object/index.md)
|
||||||
|
- [Allocating object](./object/allocate.md)
|
||||||
|
- [Detect object reallocation](./object/detect_reallocation.md)
|
||||||
|
- [Force object deallocation](./object/force_deallocation.md)
|
4
src/collections/index.md
Normal file
4
src/collections/index.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Collections recipes
|
||||||
|
|
||||||
|
A collection of `how-to`s for working with Acedia's collections:
|
||||||
|
`ArrayList` and `HashTable` classes.
|
74
src/collections/iterators.md
Normal file
74
src/collections/iterators.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Using iterators
|
||||||
|
|
||||||
|
## Simple iteration
|
||||||
|
|
||||||
|
For both `ArrayList` and `HashTable`:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
function ListKeyValue(Collection data)
|
||||||
|
{
|
||||||
|
local AcediaObject key, value;
|
||||||
|
local CollectionIterator iter;
|
||||||
|
|
||||||
|
for (iter = data.Iterate(); !iter.HasFinished(); iter.Next())
|
||||||
|
{
|
||||||
|
key = iter.GetKey();
|
||||||
|
value = iter.Get();
|
||||||
|
// Do what you want here
|
||||||
|
_.memory.Free(key);
|
||||||
|
_.memory.Free(value);
|
||||||
|
}
|
||||||
|
iter.FreeSelf();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Skipping `none` values
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
function ListKeyValue(Collection data)
|
||||||
|
{
|
||||||
|
local AcediaObject key, value;
|
||||||
|
local CollectionIterator iter;
|
||||||
|
|
||||||
|
iter = data.Iterate().LeaveOnlyNotNone();
|
||||||
|
while (!iter.HasFinished())
|
||||||
|
{
|
||||||
|
key = iter.GetKey();
|
||||||
|
value = iter.Get();
|
||||||
|
// Do what you want here
|
||||||
|
_.memory.Free(key);
|
||||||
|
_.memory.Free(value);
|
||||||
|
iter.Next();
|
||||||
|
}
|
||||||
|
iter.FreeSelf();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Iteration over `Text` keys only
|
||||||
|
|
||||||
|
Text keys are only relevant for `HashTable`s:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
function ListKeyValue(HashTable data)
|
||||||
|
{
|
||||||
|
local AcediaObject key, value;
|
||||||
|
local Text textKey;
|
||||||
|
local CollectionIterator iter;
|
||||||
|
|
||||||
|
for (iter = data.Iterate(); !iter.HasFinished(); iter.Next())
|
||||||
|
{
|
||||||
|
key = iter.GetKey();
|
||||||
|
value = iter.Get();
|
||||||
|
textKey = Text(key);
|
||||||
|
if (textKey != none)
|
||||||
|
{
|
||||||
|
Log(textKey.ToString() $ ":"
|
||||||
|
@ _.text.IntoString(_.json.Print(value)));
|
||||||
|
}
|
||||||
|
_.memory.Free(key);
|
||||||
|
_.memory.Free(value);
|
||||||
|
// `textKey` is the same reference as `key`!
|
||||||
|
}
|
||||||
|
iter.FreeSelf();
|
||||||
|
}
|
||||||
|
```
|
53
src/collections/storing_builtin.md
Normal file
53
src/collections/storing_builtin.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Storing built-in values
|
||||||
|
|
||||||
|
## Storing in the array
|
||||||
|
|
||||||
|
`ArrayList` serves the role of the regular array and storing built-in value data
|
||||||
|
inside it is simple:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
local ArrayList data;
|
||||||
|
|
||||||
|
data = _.collections.EmptyArrayList();
|
||||||
|
// Add as the last element
|
||||||
|
data.AddFloat(-2.5);
|
||||||
|
// Set at a particular index (length will be auto-adjusted)
|
||||||
|
data.Set(2, "just a string");
|
||||||
|
|
||||||
|
Log("data[0] =" @ data.GetFloat(0)); // data[0] = -2.5
|
||||||
|
Log("data[1] =" @ data.IsNone(1)); // data[1] = true
|
||||||
|
Log("data[2] =" @ data.GetString(2)); // data[2] = just a string
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting `string` as a `Text`
|
||||||
|
|
||||||
|
By default Acedia's collections use `Text` to store `string`s, so we can also
|
||||||
|
get their values as `Text`:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
local Text textInstance;
|
||||||
|
local ArrayList data;
|
||||||
|
|
||||||
|
data = _.collections.EmptyArrayList();
|
||||||
|
data.SetString(0, "Hello, world!");
|
||||||
|
textInstance = data.GetText(0);
|
||||||
|
Log("data[0] =" @ textInstance.ToString());
|
||||||
|
// Same as any object returned by a function, `textInstance` must be released
|
||||||
|
textInstance.FreeSelf();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Storing values by keys
|
||||||
|
|
||||||
|
For storing values using keys instead of numeric indices, the Acedia's way is to
|
||||||
|
use `HashTable`:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
local HashTable data;
|
||||||
|
|
||||||
|
data = _.collections.EmptyHashTable();
|
||||||
|
data.SetBool(P("Deal damage?"), true);
|
||||||
|
data.SetInt(P("Damage amount"), 9001);
|
||||||
|
|
||||||
|
Log("Deal damage?" @ data.GetFloat(P("Deal damage?"))); // Deal damage? true
|
||||||
|
Log("Damage amount:" @ data.IsNone(1)); // Damage amount: 9001
|
||||||
|
```
|
123
src/collections/using_json.md
Normal file
123
src/collections/using_json.md
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# Using JSON data
|
||||||
|
|
||||||
|
JSON data can be stored inside Acedia's collections using only built-in values
|
||||||
|
types and `ArrayList`/`HashTable` collections:
|
||||||
|
|
||||||
|
* JSON's *null* can be stored as `none`;
|
||||||
|
* JSON's *true*/*false* can be stored as `bool`'
|
||||||
|
* JSON's *number* can be stored as either `int` or `float` (actually Acedia also
|
||||||
|
contains `BigInt` type for storing arbitrarily large integer values, but it
|
||||||
|
isn't yet implemented into JSON parsing);
|
||||||
|
* JSON's *string* can be stored as `string`/`Text`/`MutableText`;
|
||||||
|
* JSON's *array* can be stored as an `ArrayList`;
|
||||||
|
* JSON's *object* can be stored as `HashTable` with `Text` keys.
|
||||||
|
|
||||||
|
> **NOTE:** JSON *does not* have a separate undefined type and Acedia uses
|
||||||
|
> `none` instead of the missing values.
|
||||||
|
|
||||||
|
## Converting JSON into Acedia collections
|
||||||
|
|
||||||
|
### Parsing JSON input
|
||||||
|
|
||||||
|
To get an Acedia collection from a JSON object like this one:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"innerObject": {
|
||||||
|
"my_bool": true,
|
||||||
|
"array": [
|
||||||
|
"Engine.Actor",
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
"something \"here\"": "yes",
|
||||||
|
"maybe": 0.003
|
||||||
|
},
|
||||||
|
56.6
|
||||||
|
],
|
||||||
|
"one more": {
|
||||||
|
"nope": 324532,
|
||||||
|
"whatever": false,
|
||||||
|
"o rly?": "ya rly"
|
||||||
|
},
|
||||||
|
"my_int": -9823452
|
||||||
|
},
|
||||||
|
"some_var": -7.32,
|
||||||
|
"another_var": "aye!"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
recorded in the `string` named `jsonData` you can do the following:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
local Parser parser;
|
||||||
|
local HashTable jsonObject;
|
||||||
|
|
||||||
|
parser = _.text.ParseString(jsonData);
|
||||||
|
jsonObject = HashTable(_.json.ParseWith(parser));
|
||||||
|
if (!parser.Ok()) {
|
||||||
|
// Handle errors
|
||||||
|
}
|
||||||
|
else if (!parser.Skip().HasFinished()) {
|
||||||
|
// There is more input left after parsing JSON value - is this a problem?
|
||||||
|
}
|
||||||
|
// '/innerObject/array/3/maybe' is 0.003
|
||||||
|
Log("'/innerObject/array/3/maybe' is"
|
||||||
|
@ jsonObject.GetFloatBy(P("/innerObject/array/3/maybe")));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Constructing by hand
|
||||||
|
|
||||||
|
Example of constructing the same object by hand:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
local HashTable jsonObject;
|
||||||
|
local HashTable innerObject, oneMore, anonymousObject;
|
||||||
|
local ArrayList jsonArray;
|
||||||
|
|
||||||
|
anonymousObject = _.collections.EmptyHashTable();
|
||||||
|
anonymousObject.SetString(P("something \"here\""), "yes");
|
||||||
|
anonymousObject.SetFloat(P("maybe"), 0.003);
|
||||||
|
|
||||||
|
jsonArray = _.collections.EmptyArrayList()
|
||||||
|
.AddString("Engine.Actor")
|
||||||
|
.jsonArray.AddBool(false)
|
||||||
|
.jsonArray.AddItem(none)
|
||||||
|
.jsonArray.AddItem(anonymousObject)
|
||||||
|
.jsonArray.AddFloat(56.6);
|
||||||
|
|
||||||
|
oneMore = _.collections.EmptyHashTable()
|
||||||
|
.SetString(P("o rly?"), "ya rly")
|
||||||
|
.SetFloat(P("nope"), 324532)
|
||||||
|
.SetBool(P("whatever"), false);
|
||||||
|
|
||||||
|
innerObject = _.collections.EmptyHashTable()
|
||||||
|
.SetItem(P("array"), jsonArray)
|
||||||
|
.SetItem(P("one more"), oneMore)
|
||||||
|
.SetBool(P("my_bool"), true)
|
||||||
|
.SetInt(P("my_int"), -9823452);
|
||||||
|
|
||||||
|
// Put it all together!
|
||||||
|
jsonObject = _.collections.EmptyHashTable()
|
||||||
|
.SetItem(P("innerObject"), innerObject)
|
||||||
|
.SetFloat(P("some_var"), -7.32)
|
||||||
|
.SetString(P("another_var"), "aye!");
|
||||||
|
// If you only want to keep `jsonObject`, release other references -
|
||||||
|
// they won't disappear
|
||||||
|
anonymousObject.FreeSelf();
|
||||||
|
jsonArray.FreeSelf();
|
||||||
|
oneMore.FreeSelf();
|
||||||
|
innerObject.FreeSelf();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Converting Acedia collections into JSON
|
||||||
|
|
||||||
|
Use `_.json.Print()` to get a compact JSON representation:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// {"innerObject":{"my_bool":true,"array":["Engine.Actor",false,null,{"something \"here\"":"yes","maybe":0.003},56.6],"one more":{"nope":324532,"whatever":false,"o rly?":"ya rly"},"my_int":-9823452},"some_var":-7.32,"another_var":"aye!"}
|
||||||
|
Log(_.text.IntoString(_.json.Print(jsonObject)));
|
||||||
|
```
|
||||||
|
|
||||||
|
or `_.json.PrettyPrint()` for a nice-looking (multiline, indented and colored)
|
||||||
|
result.
|
43
src/events/connecting.md
Normal file
43
src/events/connecting.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Connecting to signal
|
||||||
|
|
||||||
|
## From inside `AcediaObject` (or its child class)
|
||||||
|
|
||||||
|
Supposing you want to connect to a signal function
|
||||||
|
`_server.unreal.gameRules.OnNetDamage()`:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
_server.unreal.gameRules.OnNetDamage(self).connect = handler;
|
||||||
|
```
|
||||||
|
|
||||||
|
where `handler()` can be any function with appropriate signature:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
function int Handler(
|
||||||
|
int originalDamage,
|
||||||
|
int damage,
|
||||||
|
Pawn injured,
|
||||||
|
Pawn instigator,
|
||||||
|
Vector hitLocation,
|
||||||
|
out Vector momentum,
|
||||||
|
class<DamageType> damageType)
|
||||||
|
{
|
||||||
|
// Do whatever
|
||||||
|
return damage;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## From inside non-`AcediaObject`
|
||||||
|
|
||||||
|
Pass any other `AcediaObject` object as an argument to your signal function.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
local ServiceAnchor receiver;
|
||||||
|
|
||||||
|
receiver = ServiceAnchor(_.memory.Allocate(class'ServiceAnchor'));
|
||||||
|
_server.unreal.gameRules.OnNetDamage(receiver).connect = handler;
|
||||||
|
```
|
||||||
|
|
||||||
|
> **NOTE:** Signal *does not* keep the reference to passed argument and once
|
||||||
|
> `receiver` gets deallocated - signal will stop notifying handlers connected
|
||||||
|
> through it.
|
163
src/events/custom.md
Normal file
163
src/events/custom.md
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# Custom signals
|
||||||
|
|
||||||
|
## Simple notification events
|
||||||
|
|
||||||
|
If you need to add an event with handlers that don't take any parameters and
|
||||||
|
don't return anything, then easiest way it to use `SimpleSignal` / `SingleSlot`
|
||||||
|
classes:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
class MyEventClass extends AcediaObject;
|
||||||
|
|
||||||
|
var private SimpleSignal onMyEventSignal;
|
||||||
|
|
||||||
|
protected function Constructor()
|
||||||
|
{
|
||||||
|
onMyEventSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function Finalizer()
|
||||||
|
{
|
||||||
|
_.memory.Free(onMyEventSignal);
|
||||||
|
onMyEventSignal = none;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SimpleSlot OnMyEvent(AcediaObject receiver)
|
||||||
|
{
|
||||||
|
return SimpleSlot(onMyEventSignal.NewSlot(receiver));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suppose you want to emit the `signal` when this function is called...
|
||||||
|
public function SimpleSlot FireOffMyEvent(AcediaObject receiver)
|
||||||
|
{
|
||||||
|
// ...simply call this and all the slots will have their handlers called
|
||||||
|
onMyEventSignal.Emit();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can use `OnMyEvent()` as a *signal function*:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// To add handlers
|
||||||
|
myEventClassInstance.OnMyEvent(self).connect = handler;
|
||||||
|
// To remove handlers
|
||||||
|
myEventClassInstance.OnMyEvent(self).Disconnect();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Events with parameters
|
||||||
|
|
||||||
|
Some of the events, like `OnNetDamage()`, can take
|
||||||
|
parameters.
|
||||||
|
To create signals like that, follow the template and define new classes like so:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
class MySignal extends Signal;
|
||||||
|
|
||||||
|
public final function Emit(<PARAMETERS>)
|
||||||
|
{
|
||||||
|
local Slot nextSlot;
|
||||||
|
StartIterating();
|
||||||
|
nextSlot = GetNextSlot();
|
||||||
|
while (nextSlot != none)
|
||||||
|
{
|
||||||
|
MySlot(nextSlot).connect(<PARAMETERS>);
|
||||||
|
nextSlot = GetNextSlot();
|
||||||
|
}
|
||||||
|
CleanEmptySlots();
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultproperties
|
||||||
|
{
|
||||||
|
relatedSlotClass = class'MySlot'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
class MySlot extends Slot;
|
||||||
|
|
||||||
|
delegate connect(<PARAMETERS>)
|
||||||
|
{
|
||||||
|
DummyCall(); // This allows Acedia to cleanup slots without set handlers
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function Constructor()
|
||||||
|
{
|
||||||
|
connect = none;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function Finalizer()
|
||||||
|
{
|
||||||
|
super.Finalizer();
|
||||||
|
connect = none;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultproperties
|
||||||
|
{
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here you can use any set of parameters instead of `<PARAMETERS>`.
|
||||||
|
|
||||||
|
## Events with return values
|
||||||
|
|
||||||
|
Sometimes you want your handlers to respond in some way to the event.
|
||||||
|
You can either allow them to modify input parameters (e.g. by declaring them as
|
||||||
|
`out`) or allow them to have return value.
|
||||||
|
`OnNetDamage()`, for example, is allowed to modify incoming damage by returning
|
||||||
|
a new value.
|
||||||
|
|
||||||
|
To add `signal`s / `slot`s that handle return value use following templates:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
class MySignal extends Signal;
|
||||||
|
|
||||||
|
public final function <RETURN_TYPE> Emit(<PARAMETERS>)
|
||||||
|
{
|
||||||
|
local <RETURN_TYPE> newValue;
|
||||||
|
local Slot nextSlot;
|
||||||
|
StartIterating();
|
||||||
|
nextSlot = GetNextSlot();
|
||||||
|
while (nextSlot != none)
|
||||||
|
{
|
||||||
|
newValue = <SLOT_CLASS>(nextSlot).connect(<PARAMETERS>);
|
||||||
|
// This check is necessary before using returned value
|
||||||
|
if (!nextSlot.IsEmpty())
|
||||||
|
{
|
||||||
|
// Now handle `newValue` however you see fit
|
||||||
|
}
|
||||||
|
nextSlot = GetNextSlot();
|
||||||
|
}
|
||||||
|
CleanEmptySlots();
|
||||||
|
// Return whatever you see fit after handling all the slots
|
||||||
|
return <END_RETURN_VALUE>;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultproperties
|
||||||
|
{
|
||||||
|
relatedSlotClass = class'MySlot'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
class MySlot extends Slot;
|
||||||
|
|
||||||
|
delegate <RETURN_TYPE> connect(<PARAMETERS>)
|
||||||
|
{
|
||||||
|
DummyCall();
|
||||||
|
// Return anything you want:
|
||||||
|
// this value will be filtered inside corresponding `Signal`
|
||||||
|
// if no handler is set to the associated slot
|
||||||
|
return <???>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function Constructor()
|
||||||
|
{
|
||||||
|
connect = none;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function Finalizer()
|
||||||
|
{
|
||||||
|
super.Finalizer();
|
||||||
|
connect = none;
|
||||||
|
}
|
||||||
|
```
|
20
src/events/disconnecting.md
Normal file
20
src/events/disconnecting.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Disconnecting from signal
|
||||||
|
|
||||||
|
## From inside `AcediaObject` (or its child class)
|
||||||
|
|
||||||
|
Supposing you want to disconnect from a signal function
|
||||||
|
`_server.unreal.gameRules.OnNetDamage()`:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
_server.unreal.gameRules.OnNetDamage(self).Disconnect();
|
||||||
|
```
|
||||||
|
|
||||||
|
## From inside non-`AcediaObject`
|
||||||
|
|
||||||
|
To do that you must have passed another `AcediaObject` as an argument to
|
||||||
|
`OnNetDamage()` (or any other signal).
|
||||||
|
Supposing that object was called `receiver`:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
_server.unreal.gameRules.OnNetDamage(receiver).Disconnect();
|
||||||
|
```
|
4
src/events/index.md
Normal file
4
src/events/index.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Acedia events system
|
||||||
|
|
||||||
|
A collection of `how-to`s for working with Acedia's event system: `Signal`s and
|
||||||
|
`Slot`s.
|
155
src/feature/creating_feature.md
Normal file
155
src/feature/creating_feature.md
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
# Creating a Feature
|
||||||
|
|
||||||
|
## Prepare classes
|
||||||
|
|
||||||
|
When creating a `Mutator` you must create a child `Mutator` class, but
|
||||||
|
when creating a `Feature` you must create two classes: one for the `Feature`
|
||||||
|
itself and one for its config.
|
||||||
|
Name your config class something human-readable and your `Feature` class should
|
||||||
|
have the same name, but with '_Feature' suffix.
|
||||||
|
For example, if you want your configs to be stored inside 'MyFeatureConfig.ini',
|
||||||
|
bare minimum class skeletons are:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// MyFeature.uc
|
||||||
|
class MyFeature extends FeatureConfig
|
||||||
|
perobjectconfig
|
||||||
|
config(MyFeatureConfig);
|
||||||
|
|
||||||
|
defaultproperties
|
||||||
|
{
|
||||||
|
configName = "MyFeatureConfig"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// MyFeature_Feature.uc
|
||||||
|
class MyFeature_Feature extends Feature;
|
||||||
|
|
||||||
|
defaultproperties
|
||||||
|
{
|
||||||
|
configClass = class'MyFeature'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup config variables in config file
|
||||||
|
|
||||||
|
Config variables are meant to be added into your `FeatureConfig` class.
|
||||||
|
Assuming you want to add `bool` and `int` settings, simply add two public config
|
||||||
|
variables:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// MyFeature.uc
|
||||||
|
class MyFeature extends FeatureConfig
|
||||||
|
perobjectconfig
|
||||||
|
config(MyFeatureConfig);
|
||||||
|
|
||||||
|
var public config int levelOfAwesome;
|
||||||
|
var public config bool enableTrollEngine;
|
||||||
|
|
||||||
|
defaultproperties
|
||||||
|
{
|
||||||
|
configName = "MyFeatureConfig"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To setup their default values, instead of `defaultproperties`
|
||||||
|
define `DefaultIt()` method:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// MyFeature.uc
|
||||||
|
protected function DefaultIt()
|
||||||
|
{
|
||||||
|
levelOfAwesome = 11;
|
||||||
|
enableTrollEngine = true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conversion to/from `HashTable`
|
||||||
|
|
||||||
|
To make full use out of your `Feature`, Acedia also requires that you provide
|
||||||
|
methods to convert to and from its collection.
|
||||||
|
For our example simply add two more methods:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// MyFeature.uc
|
||||||
|
protected function HashTable ToData()
|
||||||
|
{
|
||||||
|
local HashTable data;
|
||||||
|
|
||||||
|
data = __().collections.EmptyHashTable();
|
||||||
|
data.SetInt(P("levelOfAwesome"), levelOfAwesome);
|
||||||
|
data.SetBool(P("enableTrollEngine"), enableTrollEngine);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function FromData(HashTable source)
|
||||||
|
{
|
||||||
|
if (source == none) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Second parameters should are fallback - use values from `DefaultIt()`
|
||||||
|
levelOfAwesome = source.GetInt(P("levelOfAwesome"), 11);
|
||||||
|
enableTrollEngine = source.GetBool(P("enableTrollEngine"), true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup your feature to use your config
|
||||||
|
|
||||||
|
Recommended way to do it is to duplicate your config variables in your
|
||||||
|
`Feature` class:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// MyFeature_Feature.uc
|
||||||
|
class MyFeature_Feature extends Feature;
|
||||||
|
|
||||||
|
// Not actually config variables
|
||||||
|
var public /*config*/ int levelOfAwesome;
|
||||||
|
var public /*config*/ bool enableTrollEngine;
|
||||||
|
|
||||||
|
defaultproperties
|
||||||
|
{
|
||||||
|
configClass = class'MyFeature'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add code that will handle setting config data for your `Feature`:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// MyFeature_Feature.uc
|
||||||
|
protected function SwapConfig(FeatureConfig config)
|
||||||
|
{
|
||||||
|
local MyFeature newConfig;
|
||||||
|
newConfig = MyFeature(config);
|
||||||
|
if (newConfig == none) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
levelOfAwesome = newConfig.levelOfAwesome;
|
||||||
|
enableTrollEngine = enableTrollEngine.disableTick;
|
||||||
|
// Here you can also add any logic that needs to be performed when
|
||||||
|
// values of the config variables were swapped mid-game, if you care to
|
||||||
|
// support it.
|
||||||
|
// Just beware that this method can be called when you `Feature` is
|
||||||
|
// both enabled and not (can be checked with `IsEnabled()`).
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run initialization / shutdown logic
|
||||||
|
|
||||||
|
Instead of normal Acedia's `Constructor()` or `Finalizer()`, for `Featuire`s
|
||||||
|
one should use:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
protected function OnEnabled()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function OnDisabled()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These methods are called when feature gets enabled or disabled, which can,
|
||||||
|
in theory, happen several times per game.
|
||||||
|
They aren't called when `Feature`'s config is changed mid-game, so sometimes
|
||||||
|
it is convenient to move part of the initialization logic into `SwapConfig()`.
|
18
src/feature/index.md
Normal file
18
src/feature/index.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Feature recipes
|
||||||
|
|
||||||
|
A collection of `how-to`s for working with `Feature` class.
|
||||||
|
|
||||||
|
This class is Acedia's replacement for `Mutators`: a certain subset of
|
||||||
|
functionality that can be enabled or disabled, according to server owner's
|
||||||
|
wishes.
|
||||||
|
Unlike `Mutator`s:
|
||||||
|
|
||||||
|
* There is no limit for the amount of `Feature`s that can be active
|
||||||
|
at the same time;
|
||||||
|
* They also provide built-in ability to have several different configs
|
||||||
|
that can be swapped during the runtime;
|
||||||
|
* They can be enabled / disabled during the runtime.
|
||||||
|
Achieving these points currently comes at the cost of developer having to
|
||||||
|
perform additional work;
|
||||||
|
* They are server-side and for now are not supposed to be created for
|
||||||
|
the clients.
|
42
src/feature/packaging_feature.md
Normal file
42
src/feature/packaging_feature.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Packaging a Feature
|
||||||
|
|
||||||
|
Suppose you've created your own `Feature` class and want to compile it to be
|
||||||
|
usable with Acedia.
|
||||||
|
In this how-to we will assume that your class is named `MyFeature_Feature`
|
||||||
|
(same as [here](./creating_feature.md)).
|
||||||
|
|
||||||
|
## Create regular UnrealScript mod
|
||||||
|
|
||||||
|
First step is still to prepare compilation of `*.u` file, like one would do for
|
||||||
|
any regular UnrealScript mod and put your `*.uc` script files inside.
|
||||||
|
|
||||||
|
## Create manifest
|
||||||
|
|
||||||
|
To know what is available in each package, Acedia reads its manifest.
|
||||||
|
To add a manifest create a file named exactly 'Manifest.uc' in your package:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// Manifest.uc
|
||||||
|
class Manifest extends _manifest
|
||||||
|
abstract;
|
||||||
|
|
||||||
|
defaultproperties
|
||||||
|
{
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Register feature in the manifest
|
||||||
|
|
||||||
|
To add your `Feature` into manifest, simply fill it inside `features` array:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
// Manifest.uc
|
||||||
|
class Manifest extends _manifest
|
||||||
|
abstract;
|
||||||
|
|
||||||
|
defaultproperties
|
||||||
|
{
|
||||||
|
// You can add any amount of features from a single package here
|
||||||
|
features(0) = class'MyFeature_Feature'
|
||||||
|
}
|
||||||
|
```
|
11
src/object/allocate.md
Normal file
11
src/object/allocate.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Allocating object
|
||||||
|
|
||||||
|
If you've created a new child class (say, `MyCoolObject`) of `AcediaObject` and
|
||||||
|
want to allocate it, simply do:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
local MyCoolObject instance;
|
||||||
|
instance = MyCoolObject(_.memory.Allocate(class'MyCoolObject'));
|
||||||
|
```
|
||||||
|
|
||||||
|
It is guaranteed to succeed for non-abstract classes.
|
50
src/object/detect_reallocation.md
Normal file
50
src/object/detect_reallocation.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Detect object reallocation
|
||||||
|
|
||||||
|
Sometimes you want to keep track of an object without declaring that you own its
|
||||||
|
reference with `NewRef()`, thus allowing it to get deallocated even while you
|
||||||
|
are keeping a reference to it.
|
||||||
|
How to do it in a safe way and detect that it was deallocated/reallocated?
|
||||||
|
|
||||||
|
## What to do when you start storing a reference
|
||||||
|
|
||||||
|
When you first get an instance of `AcediaObject` you'd like to store in such
|
||||||
|
a way, check if it's allocated and remember its life version:
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
var int storedReferenceLifeVersion;
|
||||||
|
var AcediaObject storedReference;
|
||||||
|
|
||||||
|
function Store(AcediaObject newReference)
|
||||||
|
{
|
||||||
|
if (newReference == none) return;
|
||||||
|
if (!newReference.IsAllocated()) return;
|
||||||
|
|
||||||
|
storedReference = newReference;
|
||||||
|
storedReferenceLifeVersion = newReference.GetLifeVersion();
|
||||||
|
// Not calling `newReference.NewRef()`, so `newReference` can get
|
||||||
|
// deallocated at any time!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## What to do when you want to check if stored reference was deallocated
|
||||||
|
|
||||||
|
Before using such reference again you simply need to check if life version has
|
||||||
|
changed.
|
||||||
|
If it did, then this instance was reallocated and repurposed and you shouldn't
|
||||||
|
use it anymore.
|
||||||
|
Othewrwise it is still the same object.
|
||||||
|
|
||||||
|
```unrealscript
|
||||||
|
function AcediaObject Get()
|
||||||
|
{
|
||||||
|
if (storedReference == none) {
|
||||||
|
return none;
|
||||||
|
}
|
||||||
|
if (storedReference.GetLifeVersion() != storedReferenceLifeVersion)
|
||||||
|
{
|
||||||
|
// This object was reallocated! Time to forget about it.
|
||||||
|
storedReference = none;
|
||||||
|
}
|
||||||
|
return storedReference;
|
||||||
|
}
|
||||||
|
```
|
10
src/object/force_deallocation.md
Normal file
10
src/object/force_deallocation.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Forcing object deallocation
|
||||||
|
|
||||||
|
`self.FreeSelf()` and `_.memory.Free()` only reduce reference counter of
|
||||||
|
the object.
|
||||||
|
But how to forcefully deallocate it?
|
||||||
|
|
||||||
|
**Just do not do that you fucking idiot**.
|
||||||
|
|
||||||
|
If you need to somehow dispose of what that object represents, simply add
|
||||||
|
a flag that marks that object as disposed.
|
9
src/object/index.md
Normal file
9
src/object/index.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# AcediaObject recipes
|
||||||
|
|
||||||
|
A collection of `how-to`s for working with `AcediaObject` class and related
|
||||||
|
topics.
|
||||||
|
|
||||||
|
Base object class to be used in Acedia instead of an `Object`.
|
||||||
|
`AcediaObject` provides access to Acedia's APIs through an accessor to
|
||||||
|
a `Global` object, built-in mechanism for storing unneeded references in
|
||||||
|
an object pool and constructor/finalizer.
|
Loading…
Reference in New Issue
Block a user