Anton Tarasenko
2 years ago
commit
4624668be3
18 changed files with 803 additions and 0 deletions
@ -0,0 +1,6 @@
|
||||
[book] |
||||
authors = ["Anton Tarasenko"] |
||||
language = "en" |
||||
multilingual = false |
||||
src = "src" |
||||
title = "Lazy cookbook" |
@ -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) |
@ -0,0 +1,4 @@
|
||||
# Collections recipes |
||||
|
||||
A collection of `how-to`s for working with Acedia's collections: |
||||
`ArrayList` and `HashTable` classes. |
@ -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(); |
||||
} |
||||
``` |
@ -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 |
||||
``` |
@ -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. |
@ -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. |
@ -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; |
||||
} |
||||
``` |
@ -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(); |
||||
``` |
@ -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. |
@ -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()`. |
@ -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. |
@ -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' |
||||
} |
||||
``` |
@ -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. |
@ -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; |
||||
} |
||||
``` |
@ -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. |
@ -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