Acedia defines its own base classes for both actor (`AcediaActor`)
Acedia defines its own base classes for both actor and non-actor objects
and non-actor objects (`AcediaObject`), better integrated into
(`AcediaActor` and `AcediaObject` respectively).
Acedia's infrastructure.
Both of them are better integrated into Acedia's infrastructure than regular
Here we will go over everything you need to understand them.`Object` and `Actor`.
objects and actors.
`AcediaObject` is especially important, since it provides support for
*object deallocation*.
In this document we will go over everything you need to know about
these classes.
## Who is responsible for objects?
## Who is responsible for objects?
If you've already read [safety rules](./safety.md) (and you should have),
If you have read [safety rules](./safety.md) document (and you should have),
then you already know about the importance of deallocation.
then you already know about the importance of deallocation.
But which objects exactly are you supposed to deallocate?
But which objects exactly are you supposed to deallocate?
Understanding what objects you are responsible for is likely the most important
Understanding what objects you are responsible for is one of the most important
concept to get when working with Acedia.
concepts to get when working with Acedia.
There are two main guidelines:
There are two main guidelines:
* **If function returns an object (as a return value or as an `out` argument) -
* **If function returns an object (as a return value or as an `out` argument) -
then this object must be deallocated by
then this object must be deallocated by
whoever called that function.**
whoever called that function.**
If you've called `_.text.Empty()`, then you must deallocate
If you've called `_.text.Empty()`, then you must deallocate
the `MutableText`object it returned.
the `MutableText`object it has returned.
Conversely, if you are implementing function that returns an object,
Conversely, if you are implementing function that returns an object,
then you must not deallocate it yourself.
then you must not deallocate it yourself.
In fact, you are expected not to use that object at all,
In fact, you are expected to not use that object at all after returning it,
since now you can't know when it will be deallocated.
since you cannot know when it will be deallocated.
* **Functions do not deallocate their arguments.**
* **Functions do not deallocate their arguments.**
If you pass an object as an argument to a function - you can expect
If you pass an object as an argument to a function - you can expect
that it won't be deallocated during that call.
that said object won't be deallocated during function's execution.
It might get *modified*, but not *deallocated*.
When implementing your own function - you should not deallocate
And, again, when implementing your own function - you should not deallocate
objects passed as its arguments.
its arguments either.
However, these guidelines should be treated as *default assumptions* and
However, these guidelines should be treated as *default assumptions* and
not *hard rules*.
not *hard rules*.
@ -36,16 +39,15 @@ not *hard rules*.
### Exceptions
### Exceptions
First guideline, for example, can be broken if returned object is supposed to
First guideline, for example, can be broken if returned object is supposed to
be shared: `_.players.GetPlayers()` returns array with references to
be shared: `_.players.GetPlayers()` returns `array<APLayer>` with references to
*player objects* (`array<APLayer>`) that aren't supposed to ever be deallocated.
*player objects* that aren't supposed to ever be deallocated.
Similarly, Acedia's collections operate by different rules:
Similarly, Acedia's collections operate by different rules:
they might still consider themselves responsible for objects returned with
they might still consider themselves responsible for objects returned with
`GetItem()`.
`GetItem()`.
Second guideline can also be broken by some of the methods for the sake of
Second guideline can also be broken by some of the methods for the sake of
convenience.
convenience.
If you need to turn a `Text` object `textToConvert` into a `string`,
If you need to turn a `Text` object into a `string`, then you can either do:
then you can either do:
```unrealscript
```unrealscript
if (textToConvert != none)
if (textToConvert != none)
@ -58,10 +60,11 @@ if (textToConvert != none)
or simply call `_.text.ToString()` that automatically deallocates its argument:
or simply call `_.text.ToString()` that automatically deallocates its argument:
`result = _.text.ToString(textToConvert)`.
`result = _.text.ToString(textToConvert)`.
> **NOTE:**
> Any such exceptions are documented (or at least should be), so simply read
> Any such exceptions are documented (or at least should be), so simply read
> the docs for functions you're using.
> the comment docs in source code for functions you're using.
> If they don't mention anything about how their arguments or return values
> If they don't mention anything about how arguments or return values
> should be treated - assume above stated guidelines.
> should be treated - assume above stated default guidelines.
## `MemoryAPI`
## `MemoryAPI`
@ -69,14 +72,12 @@ The majority, if not all, of the Acedia's objects you will be using are going to
be created by specialized methods like `_.text.FromString()`,
be created by specialized methods like `_.text.FromString()`,
`_.collections.EmptyDynamicArray()` or `_.time.StartTimer()`
`_.collections.EmptyDynamicArray()` or `_.time.StartTimer()`
and can be deallocated with `self.FreeSelf()` method.
and can be deallocated with `self.FreeSelf()` method.
However, that won't be enough if you want to create and allocate your own
However, if you want to allocate instances of your own classes,
classes, for that you'll need the help of `MemoryAPI`.
you'll need the help of `MemoryAPI`'s methods:
`_.memory.Allocate()` and `_.memory.Free()`.
They are less powerful than `new` keyword and `Spawn()` function, but perform
certain background work, necessary for Acedia to function and
Ultimately, all Acedia's objects and actors must be created with
**you should always use them for creating Acedia's objects**.
`_.memory.Allocate()` and destroyed with `_.memory.Free()`.
Ultimately, all Acedia's objects and actors are created with
`_.memory.Allocate()` and "destroyed" with `_.memory.Free()`.
For example, here is how new `Parser` is created with `_.text.NewParser()`:
For example, here is how new `Parser` is created with `_.text.NewParser()`:
```unrealscript
```unrealscript
@ -99,22 +100,26 @@ public final function FreeSelf(optional int lifeVersion)
}
}
```
```
These two functions are the most important ones in `MemoryAPI`,
If you create your own classes, derived from either
but it contains several more useful ones:
`AcediaObject` or `AcediaActor`, you must also use these functions to
create and destroy their instances.
`MemoryAPI` contains a few more useful functions:
| Function | Description |
| Function | Description |
| -------- | ----------- |
| -------- | ----------- |
| `Allocate(class<Object>, optional bool)` | Creates a new `Object` / `Actor` of a given class. `bool` argument allows to forbid reallocation, forcing creation of a new object.
| `Allocate(class<Object>, optional bool)` | Creates a new `Object` / `Actor` of a given class. `bool` argument allows to forbid reallocation, forcing creation of a new object.
| `LoadClass(Text)` | Creates a class instance from its textual representation. |
| `LoadClass(Text)` | Creates a class instance from its textual representation. |
| `AllocateByReference(Text, optional bool)` | Same as `Allocate()`, but takes textual representation of the class as an argument. |
| `AllocateByReference(Text, optional bool)` | Same as `Allocate()`, but takes textual representation of the class as an argument. |
| `Free(Object)` | Deallocates provided object. |
| `Free(Object)` | Deallocates provided object. Does not produce errors if its argument is `none`. |
| `FreeMany(array<Object>)` | Deallocates every object inside given array. |
| `FreeMany(array<Object>)` | Deallocates every object inside given array. Does not produce errors if some (or all) of them are `none`. |
| `CollectGarbage(optional bool)` | Forces garbage collection. By default also includes all deallocated (but not destroyed) objects.`bool` argument allows to skip collecting them.
| `CollectGarbage(optional bool)` | Forces garbage collection. By default also includes all deallocated (but not destroyed) objects and`bool` argument allows to skip collecting them.
> **NOTE:**`MemoryAPI` can also be used for creating objects that do not
> **NOTE:**While `MemoryAPI` can also be used for creating objects that do not
> derive from either `AcediaObject` or `AcediaActor`, but there is no point in
> derive from either `AcediaObject` or `AcediaActor`, there is no point in
> using them over `new` or `Spawn()`:
> using them over `new` or `Spawn()`:
> Acedia will not reallocate non-Acedia objects.
> Acedia's methods are overall less powerful and will not provide any benefits
| `Add(int)` | Increases length of the array by adding specified amount of new elements at the end. |
| `Add(int)` | Increases length of the array by adding specified amount of new elements at the end. |
| `Insert(int index, int count)` | Inserts `count`empty elements into the array at specified position. The indices of the following elements are increased by `count` in order to make room for the new elements. |
| `Insert(int index, int count)` | Inserts `count`zeroes into the array at specified position. The indices of the following elements are increased by `count` in order to make room for the new elements. |
| `Remove(int index, int count)` | Removes number elements from the array, starting at `index`. All elements before position and from `index + count` on are not changed, but the element indices change, - they shift to close the gap, created by removed elements. |
| `Remove(int index, int count)` | Removes number elements from the array, starting at `index`. All elements before position and from `index + count` on are not changed, but the element indices change, - they shift to close the gap, created by removed elements. |
| `RemoveIndex(int)` | Removes value at a given index, shifting all the elements that come after one place backwards. |
| `RemoveIndex(int)` | Removes value at a given index, shifting all the elements that come after one place backwards. |
| `AddItem(float)` | Adds given `float` at the end of the array, expanding it by 1 element. |
| `AddItem(float)` | Adds given `float` at the end of the array, expanding it by 1 element. |
@ -413,7 +431,7 @@ Static constructor is called for each class only once:
* Whenever first object of such class is created,
* Whenever first object of such class is created,
before its constructor is called;
before its constructor is called;
* If you want static initialization to be done earlier,
* If you want static initialization to be done earlier,
it is allowed to call static constructor manually:
it is possible to call static constructor manually:
`class'...'.static.StaticConstructor()`.
`class'...'.static.StaticConstructor()`.
> **NOTE:** Static constructor being called for your class does not guarantee it
> **NOTE:** Static constructor being called for your class does not guarantee it
@ -442,7 +460,7 @@ To have a clean level change it is important that you undo as many changes to
game's objects as you reasonably can.
game's objects as you reasonably can.
It is especially important to reset default values, unless their change is
It is especially important to reset default values, unless their change is
deliberate.
deliberate.
Here is an example used in the base `AcediaObject` class at some point:
Here is an example that was used in the base `AcediaObject` class at some point:
```unrealscript
```unrealscript
protected static function StaticFinalizer()
protected static function StaticFinalizer()
@ -461,13 +479,14 @@ protected static function StaticFinalizer()
### How allocation and deallocation works
### How allocation and deallocation works
UnrealScript lacks any practical way to destroy objects on demand:
UnrealScript lacks any practical way to destroy non-actor objects on demand:
the best one can do is remove any references to the object and wait for
the best one can do is remove any references to the object and wait for
garbage collection.
garbage collection.
But garbage collection itself is too slow and causes noticeable lag spikes
But garbage collection itself is too slow and causes noticeable lag spikes
for players, making it suitable only for cleaning objects when switching levels.
for players, making it suitable only for cleaning objects when switching levels.
To alleviate this problem, there exists a standard class `ObjectPool`
To alleviate this problem, there exists a standard class `ObjectPool`
that stores unused objects inside dynamic array until they are needed.
that stores unused objects (mostly resources such as textures) inside
dynamic array until they are needed.
Unfortunately, using a single `ObjectPool` for a large volume of objects is
Unfortunately, using a single `ObjectPool` for a large volume of objects is
impractical from performance perspective, since it stores objects of
impractical from performance perspective, since it stores objects of
@ -475,6 +494,7 @@ all classes together and each object allocation from the pool can potentially
require going through the whole array:
require going through the whole array:
```unrealscript
```unrealscript
// FILE: Engine/ObjectPool.uc
simulated function Object AllocateObject(class ObjectClass)
simulated function Object AllocateObject(class ObjectClass)
{
{
local Object Result;
local Object Result;
@ -535,7 +555,7 @@ first deallocated and then allocated again by some other code.
Then `IsAllocate()` will return `true` even though your reference is
Then `IsAllocate()` will return `true` even though your reference is
no longer valid.
no longer valid.
This issue can be solved with *life version* - `int` value that changes
This issue can be solved with a *life version* - `int` value that changes
each time object is reallocated:
each time object is reallocated:
```unrealscript
```unrealscript
@ -568,8 +588,8 @@ remember its life version value right after allocation
and then compare it to the `GetLifeVersion()`'s result.
and then compare it to the `GetLifeVersion()`'s result.
Value returned by `GetLifeVersion()` changes after each reallocation
Value returned by `GetLifeVersion()` changes after each reallocation
and won't repeat for the same object.
and won't repeat for the same object.
The only guarantee about life versions of deallocated objects is that they will
The only guarantee about life versions of objects,
be negative.
that aren't currently allocated, is that they will be negative.