Browse Source

Improve introducotry documentation

pull/8/head
Anton Tarasenko 3 years ago
parent
commit
3aa6f555cd
  1. 17
      docs/api.md
  2. 32
      docs/index.md
  3. 168
      docs/objects.md
  4. 43
      docs/safety.md

17
docs/api.md

@ -4,22 +4,21 @@ Acedia's API is our way of solving the problem of adding new *global functions*.
Examples of *global functions* are `Log()`, `Caps()`, `Abs()`, `VSize()` Examples of *global functions* are `Log()`, `Caps()`, `Abs()`, `VSize()`
and a multitude of others that you can call from anywhere in UnrealScript. and a multitude of others that you can call from anywhere in UnrealScript.
They can be accessed from anywhere because they are all declared as They can be accessed from anywhere because they are all declared as
static methods inside `Object` - a base class for any other object, static methods inside `Object` - a base class for any other class.
including actors.
Problem is, since we cannot add our own methods to the `Object`, Problem is, since we cannot add our own methods to the `Object`,
then we also can't add new global functions. then we also can't add new global functions.
The best we can do is declare new static methods in our own classes, The best we can do is declare new static methods in our own classes,
but calling them would be cumbersome: `class'glb'.static.DoIt()`. but calling them would be cumbersome: `class'glb'.static.DoIt()`.
Idea that we've used to solve this problem for Acedia is to define Idea that we've used to solve this problem for Acedia is to provide every single
a separate class that would contain all our global functions. Acedia object with an instance of a class that would contain all our
If we save an instance of this class in some local variable global functions.
`glb`, then we can simply write `glb.DoIt()` instead of We save an instance of this class in a local variable
`class'glb'.static.DoIt()`. `_`, which allows us to simply write `_.DoIt()`.
In actuality we don't just dump all of Acedia's global functions into In actuality we don't just dump all of Acedia's global functions into
one object, but group them into different APIs that can be accessed one object, but group them into different APIs that can be accessed
through `_` variable, defined in every `AcediaObject` and `AcediaActor`: through `_` variable:
```unrealscript ```unrealscript
_.text.FromString("I am here!"); // Text API _.text.FromString("I am here!"); // Text API
@ -45,7 +44,7 @@ Any class you make that derives from either `AcediaObject` or `AcediaActor`
will have `_` and `__()` defined. will have `_` and `__()` defined.
If you need to create a class that does not derive from Acedia's classes, If you need to create a class that does not derive from Acedia's classes,
but you want to make Acedia's API be available inside it, but you want to make Acedia's API be available inside it,
then you simply need to redefine them: then you simply need to redefine `_` and `__()`:
```unrealscript ```unrealscript
var Global _; var Global _;

32
docs/index.md

@ -1,21 +1,23 @@
# Acedia for mod making # Acedia for mod making
This document aims to guide you through all of Acedia's features and This document describes Acedia from the mod maker's perspective:
capabilities from the mod maker's perspective. how to use it to create new mods and how it works internally.
It consists of a brief overview of how different components fit together and It consists of a brief overview of how different components fit together and
then somewhat detailed look at each of them. then somewhat detailed look at each of them.
This document is not a reference documentation that lists and describes This document is not a reference documentation that lists and describes
every single class and method. Unfortunately, such a document does not exist every single class and method.
right now. The closest substitute for it would be Acedia's source code - Unfortunately, such a document does not exist right now.
most of the methods and classes are given brief descriptions in the comments. The closest substitute for it would be Acedia's source code - most of
the methods and classes are given brief descriptions in the comments.
They might somewhat lack in quality, since having a peer review for them They might somewhat lack in quality, since having a peer review for them
would not have been viable, but that is all I can offer. Any corrections to would not have been viable, but that is all we can offer.
them are always welcome. Any corrections to them are always welcome.
We assume that our audience is at least familiar with UnrealScript: We assume that our audience is at least familiar with UnrealScript and we cannot
Acedia's API is not stable enough for us to recommend using it to people new recommend using it to people new to the modding anyway:
to the Killing Floor modding anyway. Acedia's API is not stable enough and has certain quirks that can lead to nasty,
hard-to-catch bugs.
## What the hell is all of this? ## What the hell is all of this?
@ -28,7 +30,7 @@ It would have been an overkill if bug fixing was all Acedia would ever do and
now `Feature` is one of the Acedia's main... features, that is supposed to take now `Feature` is one of the Acedia's main... features, that is supposed to take
the role of the `Mutator` class. the role of the `Mutator` class.
What was Acedia before now is broken into three different packages: What Acedia was before is now broken into three different packages:
* AcediaCore - package that defines base classes, required for other * AcediaCore - package that defines base classes, required for other
Acedia packages to work correctly; Acedia packages to work correctly;
@ -41,14 +43,12 @@ The topic of this document is only AcediaCore - a base class library.
## Getting started ## Getting started
First of all, go read about [safety rules](./safety.md). First of all, go read about [safety rules](./safety.md).
They don't go into much detail, so don't worry if you don't understand They make a good introduction and will warn you about otherwise very likely
everything - you can read on each specific topic later.
But they make a good introduction and will warn you about otherwise very likely
mistakes that could lead to rather nasty consequences. mistakes that could lead to rather nasty consequences.
After you've familiarized yourself with safety rules, you can skip to reading After you've familiarized yourself with safety rules, you can skip to reading
any topic of interest, but we strongly recommend that you first read up on about any topic of interest, but we strongly recommend that you first read up on
the fundamental topics: the fundamental topics:
[what is API](./api.md), [what is API](./api.md),
at least non-advanced topics of [Acedia's objects / actors](./objects.md) at least non-advanced topics of [Acedia's objects / actors](./objects.md)
about and signal / slot system. and about signal / slot system.

168
docs/objects.md

@ -1,34 +1,37 @@
# `AcediaObject` and `AcediaActor` # `AcediaObject` and `AcediaActor`
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
> for non-Acedia objects.
## Constructors and finalizers ## Constructors and finalizers
@ -125,12 +130,11 @@ and
[finalizers](https://en.wikipedia.org/wiki/Finalizer). [finalizers](https://en.wikipedia.org/wiki/Finalizer).
*Constructor* is a method that's called on object after it's created, *Constructor* is a method that's called on object after it's created,
preparing it for use. preparing it for use.
In Acedia *Finalizer* is a method that's called when object is deallocated *Finalizer* is a method that's called when object is deallocated
(or actor is destroyed) and can be used to clean up any used resources. (or actor is destroyed) and can be used to clean up any used resources.
> Technically, right now *destructor* might be a better terminology for Acedia's > Technically, right now *destructor* might be a better terminology for Acedia's
> finalizers, but, if development is not halted, current name would eventually > finalizers.
> become a better fit.
A good and simple example is from the `ATradingComponent` that A good and simple example is from the `ATradingComponent` that
allocates necessary objects inside its constructor and deallocates them in allocates necessary objects inside its constructor and deallocates them in
@ -166,10 +170,11 @@ overload `Constructor()` and `Finalizer()` methods (they are defined in both
## Object equality and object hash ## Object equality and object hash
Comparing object variable with `==` operator simply checks if they refer to Comparing object variables with `==` operator checks *reference equality*:
the exact same object. whether variables refer to the exact same object.
But sometimes we want a comparison that compares the content of two objects But sometimes we want to implement *value equality* check - a comparison for
instead: like checking that two different `Text`s store the exact same data. the contents of two objects, e.g. checking that two different `Text`s
store the exact same data.
Acedia provides an alternative way to compare two objects - `IsEqual()` method. Acedia provides an alternative way to compare two objects - `IsEqual()` method.
Its default implementation corresponds to that of `==` operator: Its default implementation corresponds to that of `==` operator:
@ -180,15 +185,17 @@ public function bool IsEqual(Object other)
} }
``` ```
but can be redefined, as long as it obeys following rules: But it can be redefined, as long as it obeys following rules:
* `a.IsEqual(a) == true`; * `a.IsEqual(a) == true`;
* `a.IsEqual(b)` if and only if `b.IsEqual(a)`; * `a.IsEqual(b)` if and only if `b.IsEqual(a)`;
* `none` is only equal to `none;
* Result of `a.IsEqual(b)` does not change unless one of the objects gets * Result of `a.IsEqual(b)` does not change unless one of the objects gets
deallocated. deallocated.
Because of last rule two `MutableText`s cannot be compared base on their content Because of the last rule, `IsEqual()` cannot compare two `MutableText`s based on
since their contents can change without deallocation. their contents, since they can change without deallocation
(unlike contents of an immutable `Text`).
Reimplementing `IsEqual()` method also requires you to reimplement how object's Reimplementing `IsEqual()` method also requires you to reimplement how object's
[hash value](https://en.wikipedia.org/wiki/Hash_function) is calculated. [hash value](https://en.wikipedia.org/wiki/Hash_function) is calculated.
@ -196,7 +203,8 @@ Reimplementing `IsEqual()` method also requires you to reimplement how object's
Several different objects can have the same hash value and equal objects *must* Several different objects can have the same hash value and equal objects *must*
have the same hash value. have the same hash value.
By default, Acedia's objects simply use randomly generated value as their hash. By default, Acedia's objects simply use randomly generated value as their hash,
determined at the moment of their creation.
This can be changed by reimplementing `CalculateHashCode()` method. This can be changed by reimplementing `CalculateHashCode()` method.
Every object will only call it once to cache it for `GetHashCode()`: Every object will only call it once to cache it for `GetHashCode()`:
@ -230,28 +238,34 @@ protected function int CalculateHashCode()
} }
``` ```
This makes sure that two `Text`s with equal contents have the same hash value.
## Boxing ## Boxing
Last important topic to go over is Last important topic to go over is
[boxing]( [boxing](
https://en.wikipedia.org/wiki/Object_type_(object-oriented_programming)#Boxing), https://en.wikipedia.org/wiki/Object_type_(object-oriented_programming)#Boxing):
a process of turning primitive types such as `bool`, `byte`, `int` or `float` a process of turning primitive types such as `bool`, `byte`, `int` or `float`
into objects. into objects.
The concept is very simple - we create a *box* object, which is just an object The concept is very simple, we create a *box* object - an object
that stores a single primitive value and could be implemented kind of like that: that stores a single primitive value.
It could be implemented like that:
```unrealscript ```unrealscript
class MyBox extends Object; class MyBox extends Object;
var float value; var float value;
``` ```
Except Acedia's boxes are *immutable* - their value cannot change once However, Acedia's boxes are *immutable* - their value cannot change once
the box was created. the box was created.
This means that they store their value in the private field and provide access
to it through the appropriate getter method.
Boxes were introduced because they allowed creation of general collections: Boxes were introduced because they allowed creation of general collections:
Acedia's collections can only store `AcediaObject`, but thanks to boxing Acedia's collections can only store `AcediaObject`, but, thanks to boxing,
any value can be turned into `AcediaObject` and stored in the collection. any value can be turned into an `AcediaObject` and stored in the collection.
For native primitive types they can be created with either `BoxAPI` or manually: For native primitive types boxes can be created with either `BoxAPI` or
manually:
```unrealscript ```unrealscript
local IntBox box1; local IntBox box1;
@ -284,10 +298,10 @@ Log("Int value:" @ ref1.Get()); // Int value: -89
Log("Float value:" @ ref2.Get()); // Float value: 0.56 Log("Float value:" @ ref2.Get()); // Float value: 0.56
``` ```
The most important difference between boxes and references concerns how their The most important difference between boxes and references concerns
`IsEqual()` and `GetHash()` are implemented: implementation of their `IsEqual()` and `GetHash()` methods:
* Since boxes redefine `IsEqual()` and `GetHash()` to depend on the stored value. * Boxes redefine `IsEqual()` and `GetHash()` to depend on the stored value.
Since value inside the box cannot change, then there is no problem to base Since value inside the box cannot change, then there is no problem to base
equality and hash on it. equality and hash on it.
* References do not redefine `IsEqual()` / `GetHash()` and behave like any * References do not redefine `IsEqual()` / `GetHash()` and behave like any
@ -312,13 +326,15 @@ Log("Refs hash equality:" @ (ref1.GetHash() == ref2.GetHash()));
``` ```
> **NOTE:** For `string`s the role of boxes and references is performed by > **NOTE:** For `string`s the role of boxes and references is performed by
> `Text` and `MutableText` classes that are discussed separately. > `Text` and `MutableText` classes that are discussed elsewhere.
### Actor references with `NativeActorRef` ### Actor references with `NativeActorRef`
As was explained in [safety rules](./safety.md), storing references to actors As was explained in [safety rules](./safety.md), storing references to actors
inside objects is a bad idea. directly inside objects is a bad idea.
Actor boxes and references provide us with a safe way to do that: The safe way to do it are *actor references*:
`ActorRef` for Acedia's actors and `NativeActorRef` for any kind of actors.
Actor returned by their `Get()` method is guaranteed to be safe to use:
```unrealscript ```unrealscript
class MyObject extends AcediaObject; class MyObject extends AcediaObject;
@ -363,14 +379,16 @@ function DoWork()
// <Some code that might `Destroy()` our pawn> // <Some code that might `Destroy()` our pawn>
// ^ After destroying a pawn, // ^ After destroying a pawn,
// `myPawn` local variable might go "bad" and cause crashes, // `myPawn` local variable might go "bad" and cause crashes,
// so it's a good idea to update it from safe `pawnReference`: // so it's a good idea to "update" it from the safe `pawnReference`:
myPawn = GetMyPawn(); myPawn = GetMyPawn();
if (myPawn != none) {
myPawn.health += 10; myPawn.health += 10;
}
} }
``` ```
Actor boxes do not exist, since we cannot guarantee that value stored inside Actor boxes do not exist, since we cannot guarantee that value inside them will
them will never change - destroying stored actor will always reset it to `none`. never change - destroying stored actor will always reset it to `none`.
### Array boxes and references ### Array boxes and references
@ -379,8 +397,8 @@ of value, including `array<...>`s and `struct`s.
Acedia provides such classes for arrays of primitive types out of the box. Acedia provides such classes for arrays of primitive types out of the box.
They can be useful for passing huge arrays between objects and functions They can be useful for passing huge arrays between objects and functions
by reference, without copying their entire data every time. by reference, without copying their entire data every time.
They also provide quite a few several convenience methods. They also provide several convenience methods - here is a list for
Here is a list for `FloatArrayRef` as an example: `FloatArrayRef`'s methods as an example:
| Method | Description | | Method | Description |
| ------ | ----------- | | ------ | ----------- |
@ -392,7 +410,7 @@ Here is a list for `FloatArrayRef` as an example:
| `SetLength(int)` | Resizes stored array, doing nothing on negative input. | | `SetLength(int)` | Resizes stored array, doing nothing on negative input. |
| `Empty()` | Empties stored array. | | `Empty()` | Empties stored array. |
| `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.
### Customizing object pools for your classes ### Customizing object pools for your classes

43
docs/safety.md

@ -1,9 +1,5 @@
# Acedia's safety rules # Acedia's safety rules
To work with Acedia it is necessary to understand its object management:
what it is and why it exists.
Our aim here is to provide a brief introduction using `Text` as an example.
When working with UnrealScript one can distinguish between following types When working with UnrealScript one can distinguish between following types
of variables: of variables:
@ -11,36 +7,36 @@ of variables:
2. Actors: objects that have `Actor` as their parent; 2. Actors: objects that have `Actor` as their parent;
3. Non-actor objects: object of any class not derived derived from the `Actor`. 3. Non-actor objects: object of any class not derived derived from the `Actor`.
Most of the mods mainly use first and second type, but Acedia makes heavy use of Most of the mods mainly use first and second type, but we make heavy use of
the third one. the third one.
This allows Acedia to provide convenient interfaces for its functionality and This allows Acedia to provide convenient interfaces for its functionality and
simplify implementation of its features. simplify implementation of its features.
However it also creates several new problems, normally not encountered by However it also creates several new problems, normally not encountered by
other mods. other mods.
Here we will introduce and briefly explain several rules that should be followed Here we will introduce and briefly explain three main rules that you need
to properly use Acedia. to keep in mind when working with Acedia.
## Do not store references to actors in non-actor objects ## Rule 1: Do not store references to actors in non-actor objects
Storing actors in non-actor objects is a bad idea and can lead to Storing actors in non-actor objects is a bad idea and can lead to
game/server crashes. game/server crashes.
If you are interested in the explanation of why, you can read discussion If you are interested in the explanation of why, you can read discussion
[here](https://wiki.beyondunreal.com/Legacy:Creating_Actors_And_Objects). [here](https://wiki.beyondunreal.com/Legacy:Creating_Actors_And_Objects).
This isn't really a problem in most mutators, since they store references This isn't really a problem in most mutators, since they store references
to actors (`KFMonster`, `KFPlayerController`, ...) to actors (`KFMonster`, `KFPlayerController`, ...)
inside other actors (`Mutator`, `GameType`, ...); inside other actors (`Mutator`, `GameType`, ...).
however, in Acedia almost everything is a non-actor object, which can cause However, in Acedia almost everything is a non-actor object, so simply having
a lot of trouble, since even a simple check like `myActor != none` actor variables can be volatile:
can lead to a crash. even a simple check `myActor != none` can lead to a crash if `myActor`
was destroyed recently enough.
Acedia's goal is to provide you with enough wrapper API, so that you don't have
to reference actors directly. Acedia's end goal is to provide you with enough wrappers,
We are a long way away from that goal, so for whenever these API are not enough, so that you don't have to reference actors directly.
Acedia provides a way to work with actors safely We are a long way away from that, so for whenever our API is not enough,
we also provide a safer way to work with actors inside objects
(see [Actor references with `NativeActorRef`](./objects.md)). (see [Actor references with `NativeActorRef`](./objects.md)).
## Take care to explicitly free unneeded objects ## Rule 2: Take care to explicitly free unneeded objects
We'll illustrate this point with `Text` - Acedia's own type that is used as We'll illustrate this point with `Text` - Acedia's own type that is used as
a replacement for `string`. Consider following simple code: a replacement for `string`. Consider following simple code:
@ -110,7 +106,12 @@ This concerns not only `Text`, but almost every single Acedia's object.
To efficiently use Acedia, you must learn to deallocate objects that are To efficiently use Acedia, you must learn to deallocate objects that are
not going to be used anymore. not going to be used anymore.
## You should *never ever* use anything you've deallocated ## Rule 3: You should *never ever* use anything you've deallocated
> **IMPORTANT:**
> This is the most important rule - violating will create bugs that
> are extremely hard to catch.
> And possibility of such bugs is the biggest downside of using Acedia.
If `Text` variable from above wasn't local, but global variable, then we'd have If `Text` variable from above wasn't local, but global variable, then we'd have
to add one more instruction `message = none`: to add one more instruction `message = none`:
@ -133,7 +134,7 @@ however, some other piece of code might re-allocate that object
and use it for something completely different. and use it for something completely different.
This means unpredictable and undefined behavior for everybody. This means unpredictable and undefined behavior for everybody.
To avoid creating with this problem - everyone must always make sure to To avoid creating with this problem - everyone must always make sure to
*forget* about objects you've deallocated by setting your references to `none`. *forget* about objects you've deallocated by setting their references to `none`.
> **NOTE:** This also means that you should not deallocate the same object > **NOTE:** This also means that you should not deallocate the same object
> more than once. > more than once.

Loading…
Cancel
Save