From 25f9b447a8b7a3b7e319a6d8b856fa1564809f5c Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Thu, 19 Aug 2021 02:55:05 +0700 Subject: [PATCH] Update docs --- docs/api.md | 61 +++++ docs/index.md | 28 ++- docs/objects.md | 588 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/safety.md | 21 +- 4 files changed, 679 insertions(+), 19 deletions(-) create mode 100644 docs/api.md create mode 100644 docs/objects.md diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..6a2b612 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,61 @@ +# Acedia's API + +Acedia's API is our way of solving the problem of adding new *global functions*. +Examples of *global functions* are `Log()`, `Caps()`, `Abs()`, `VSize()` +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 +static methods inside `Object` - a base class for any other object, +including actors. +Problem is, since we cannot add our own methods to the `Object`, +then we also can't add new global functions. +The best we can do is declare new static methods in our own classes, +but calling them would be cumbersome: `class'glb'.static.DoIt()`. + +Idea that we've used to solve this problem for Acedia is to define +a separate class that would contain all our global functions. +If we save an instance of this class in some local variable +`glb`, then we can simply write `glb.DoIt()` instead of +`class'glb'.static.DoIt()`. + +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 +through `_` variable, defined in every `AcediaObject` and `AcediaActor`: + +```unrealscript +_.text.FromString("I am here!"); // Text API +_.alias.ResolveColor("blue"); // Alias API +_.collections.EmptyDynamicArray(); // Collections API +_.memory.Allocate(class'SimpleSignal'); // Memory API +``` + +`_` can't be accessed in static methods, since only default values are +available in them. +Since writing `default._` would also be bulky, `AcediaObject` and `AcediaActor` +provide a static method `public static final function Global __()` +that is always available: + +```unrealscript +__().text.FromString("I am here!"); +__().alias.ResolveColor("blue"); +__().collections.EmptyDynamicArray(); +__().memory.Allocate(class'SimpleSignal'); +``` + +Any class you make that derives from either `AcediaObject` or `AcediaActor` +will have `_` and `__()` defined. +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, +then you simply need to redefine them: + +```unrealscript +var Global _; + +public static final function Global __() +{ + return class'Global'.static.GetInstance(); +} + +// ... +// Set `_`'s value somewhere before using your class: +_ = class'Global'.static.GetInstance(); +``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 1096b66..f93a7f0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,9 +1,9 @@ # Acedia for mod making -This document aim to guide you through all of Acedia's features and -capabilities from the mod maker perspective. It consists of a brief overview of -how different components fit together and then somewhat detailed look at each -of them. +This document aims to guide you through all of Acedia's features and +capabilities from the mod maker's perspective. +It consists of a brief overview of how different components fit together and +then somewhat detailed look at each of them. This document is not a reference documentation that lists and describes every single class and method. Unfortunately, such a document does not exist @@ -13,9 +13,9 @@ 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 them are always welcome. -We assume that our audience is at least familiar with UnrealScript programming. +We assume that our audience is at least familiar with UnrealScript: Acedia's API is not stable enough for us to recommend using it to people new -to the Killing Floor modding. +to the Killing Floor modding anyway. ## What the hell is all of this? @@ -23,9 +23,10 @@ Acedia 0.1 was a small mutator that fixed game-breaking bugs and what Acedia is now might seem like a huge departure from that. But this development was more or less planned even before version 0.1 release. In particular, Acedia 0.1 had already included a `Feature` class that was used -to pick what bug fixes to enable. 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 the role of the `Mutator` class. +to pick what bug fixes should be enabled. +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 +the role of the `Mutator` class. What was Acedia before now is broken into three different packages: @@ -37,10 +38,17 @@ Acedia's `Feature`s; The topic of this document is only AcediaCore - a base class library. -## Functionality of AcediaCore +## Getting started 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 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. + +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 +the fundamental topics: +[what is API](./api.md), +at least non-advanced topics of [Acedia's objects / actors](./objects.md) +about and signal / slot system. diff --git a/docs/objects.md b/docs/objects.md new file mode 100644 index 0000000..28c3382 --- /dev/null +++ b/docs/objects.md @@ -0,0 +1,588 @@ +# `AcediaObject` and `AcediaActor` + +Acedia defines its own base classes for both actor (`AcediaActor`) +and non-actor objects (`AcediaObject`), better integrated into +Acedia's infrastructure. +Here we will go over everything you need to understand them.`Object` and `Actor`. + +## Who is responsible for objects? + +If you've already read [safety rules](./safety.md) (and you should have), +then you already know about the importance of deallocation. +But which objects exactly are you supposed to deallocate? +Understanding what objects you are responsible for is likely the most important +concept to get when working with Acedia. +There are two main guidelines: + +* **If function returns an object (as a return value or as an `out` argument) - + then this object must be deallocated by + whoever called that function.** + If you've called `_.text.Empty()`, then you must deallocate + the `MutableText`object it returned. + Conversely, if you are implementing function that returns an object, + then you must not deallocate it yourself. + In fact, you are expected not to use that object at all, + since now you can't know when it will be deallocated. +* **Functions do not deallocate their arguments.** + If you pass an object as an argument to a function - you can expect + that it won't be deallocated during that call. + It might get *modified*, but not *deallocated*. + And, again, when implementing your own function - you should not deallocate + its arguments either. + +However, these guidelines should be treated as *default assumptions* and +not *hard rules*. + +### Exceptions + +First guideline, for example, can be broken if returned object is supposed to +be shared: `_.players.GetPlayers()` returns array with references to +*player objects* (`array`) that aren't supposed to ever be deallocated. +Similarly, Acedia's collections operate by different rules: +they might still consider themselves responsible for objects returned with +`GetItem()`. + +Second guideline can also be broken by some of the methods for the sake of +convenience. +If you need to turn a `Text` object `textToConvert` into a `string`, +then you can either do: + +```unrealscript +if (textToConvert != none) +{ + result = textToConvert.ToPlainString(); + textToConvert.FreeSelf(); +} +``` + +or simply call `_.text.ToString()` that automatically deallocates its argument: +`result = _.text.ToString(textToConvert)`. + +> Any such exceptions are documented (or at least should be), so simply read +> the docs for functions you're using. +> If they don't mention anything about how their arguments or return values +> should be treated - assume above stated guidelines. + +## `MemoryAPI` + +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()`, +`_.collections.EmptyDynamicArray()` or `_.time.StartTimer()` +and can be deallocated with `self.FreeSelf()` method. +However, that won't be enough if you want to create and allocate your own +classes, for that you'll need the help of `MemoryAPI`. + +They are less powerful than `new` keyword and `Spawn()` function, but perform +certain background work, necessary for Acedia to function and +**you should always use them for creating Acedia's objects**. +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()`: + +```unrealscript +public final function Parser NewParser() +{ + return Parser(_.memory.Allocate(class'Parser')); +} +``` + +and `self.FreeSelf()` is actually defined in `AcediaObject` +and `AcediaActor` as follows (ignore parts about life versions for now, +they will be explained in sections below): + +```unrealscript +public final function FreeSelf(optional int lifeVersion) +{ + if (lifeVersion <= 0 || lifeVersion == GetLifeVersion()) { + _.memory.Free(self); + } +} +``` + +These two functions are the most important ones in `MemoryAPI`, +but it contains several more useful ones: + +| Function | Description | +| -------- | ----------- | +| `Allocate(class, 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. | +| `AllocateByReference(Text, optional bool)` | Same as `Allocate()`, but takes textual representation of the class as an argument. | +| `Free(Object)` | Deallocates provided object. | +| `FreeMany(array)` | Deallocates every object inside given array. | +| `CollectGarbage(optional bool)` | Forces garbage collection. By default also includes all deallocated (but not destroyed) objects. `bool` argument allows to skip collecting them. + +> **NOTE:** `MemoryAPI` can also be used for creating objects that do not +> derive from either `AcediaObject` or `AcediaActor`, but there is no point in +> using them over `new` or `Spawn()`: +> Acedia will not reallocate non-Acedia objects. + +## Constructors and finalizers + +Both `AcediaObject` and `AcediaActor` support +[constructors]( +https://en.wikipedia.org/wiki/Constructor_(object-oriented_programming)) +and +[finalizers](https://en.wikipedia.org/wiki/Finalizer). +*Constructor* is a method that's called on object after it's created, +preparing it for use. +In Acedia *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. + +> Technically, right now *destructor* might be a better terminology for Acedia's +> finalizers, but, if development is not halted, current name would eventually +> become a better fit. + +A good and simple example is from the `ATradingComponent` that +allocates necessary objects inside its constructor and deallocates them in +its finalizer: + +```unrealscript +protected function Constructor() +{ + onStartSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal')); + onEndSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal')); + onTraderSelectSignal = Trading_OnSelect_Signal( + _.memory.Allocate(class'Trading_OnSelect_Signal')); +} + +protected function Finalizer() +{ + _.memory.Free(onStartSignal); + _.memory.Free(onEndSignal); + _.memory.Free(onTraderSelectSignal); + onStartSignal = none; + onEndSignal = none; + onTraderSelectSignal = none; +} +``` + +To use constructors and finalizers in your own classes you simply need to +overload `Constructor()` and `Finalizer()` methods (they are defined in both +`AcediaObject` and `AcediaActor`), just like in the example above. + +> Acedia's constructors do not take parameters and because of that some classes +> also define `Initialize()` method that is required to be used right after +> an object was allocated. + +## Object equality and object hash + +Comparing object variable with `==` operator simply checks if they refer to +the exact same object. +But sometimes we want a comparison that compares the content of two objects +instead: like checking that two different `Text`s store the exact same data. +Acedia provides an alternative way to compare two objects - `IsEqual()` method. +Its default implementation corresponds to that of `==` operator: + +```unrealscript +public function bool IsEqual(Object other) +{ + return (self == other); +} +``` + +but can be redefined, as long as it obeys following rules: + +* `a.IsEqual(a) == true`; +* `a.IsEqual(b)` if and only if `b.IsEqual(a)`; +* Result of `a.IsEqual(b)` does not change unless one of the objects gets + deallocated. + +Because of last rule two `MutableText`s cannot be compared base on their content +since their contents can change without deallocation. + +Reimplementing `IsEqual()` method also requires you to reimplement how object's +[hash value](https://en.wikipedia.org/wiki/Hash_function) is calculated. +*Hash value* is a an `int` associated with an object. +Several different objects can have the same hash value and equal objects *must* +have the same hash value. + +By default, Acedia's objects simply use randomly generated value as their hash. +This can be changed by reimplementing `CalculateHashCode()` method. +Every object will only call it once to cache it for `GetHashCode()`: + +```unrealscript +public final function int GetHashCode() +{ + if (_hashCodeWasCached) { + return _cachedHashCode; + } + _hashCodeWasCached = true; + _cachedHashCode = CalculateHashCode(); + return _cachedHashCode; +} +``` + +As an example, here is `Text`'s definition that calculates hash based on +the contents: + +```unrealscript +protected function int CalculateHashCode() +{ + local int i; + local int hash; + hash = 5381; + for (i = 0; i < codePoints.length; i += 1) + { + // hash * 33 + codePoints[i] + hash = ((hash << 5) + hash) + codePoints[i]; + } + return hash; +} +``` + +## Boxing + +Last important topic to go over is +[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` +into objects. +The concept is very simple - we create a *box* object, which is just an object +that stores a single primitive value and could be implemented kind of like that: + +```unrealscript +class MyBox extends Object; +var float value; +``` + +Except Acedia's boxes are *immutable* - their value cannot change once +the box was created. + +Boxes were introduced because they allowed creation of general collections: +Acedia's collections can only store `AcediaObject`, but thanks to boxing +any value can be turned into `AcediaObject` and stored in the collection. +For native primitive types they can be created with either `BoxAPI` or manually: + +```unrealscript +local IntBox box1; +local FloatBox box2; +// Created with `BoxAPI` +box1 = _.box.int(7); +// Allocated and initialized manually +box2 = FloatBox(_.memory.Allocate(class'FloatBox')); +box2.Initialize(-2.48); // Must be done immediately after allocation! +// Works the same +Log("Int value:" @ box1.Get()); // Int value: 7 +Log("Float value:" @ box2.Get()); // Float value: -2.48 +``` + +Immutable boxes also have a counterpart - mutable *references* that also +provide `Set()` method: + +```unrealscript +local IntRef ref1; +local FloatRef ref2; +// Created with `BoxAPI` +ref1 = _.ref.int(7); +// Allocated and initialized manually +ref2 = FloatRef(_.memory.Allocate(class'FloatRef')); +ref2.Initialize(-2.48); // Must be done immediately after allocation! +// Change values +ref1.Set(-89); +ref2.Set(0.56); +Log("Int value:" @ ref1.Get()); // Int value: -89 +Log("Float value:" @ ref2.Get()); // Float value: 0.56 +``` + +The most important difference between boxes and references concerns how their +`IsEqual()` and `GetHash()` are implemented: + +* Since 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 + equality and hash on it. +* References do not redefine `IsEqual()` / `GetHash()` and behave like any + other object - their hash is random and they are only equal to themselves. + +```unrealscript +local ByteBox box1, box2; +local ByteRef ref1, ref2; +box1 = _.box.byte(56); +box2 = _.box.byte(56); +ref1 = _.ref.byte(247); +ref2 = _.ref.byte(247); +// Boxes equality: true +Log("Boxes equality:" @ (box1.IsEqual(box2))); +// Boxes hash equality: true +Log("Boxes hash equality:" @ (box1.GetHash() == box2.GetHash())); +// Refs equality: false +Log("Refs equality:" @ (ref1.IsEqual(ref2))); +// Refs hash equality: false +// (that's the most likely result, but it can actually be `true` by pure chance) +Log("Refs hash equality:" @ (ref1.GetHash() == ref2.GetHash())); +``` + +> **NOTE:** For `string`s the role of boxes and references is performed by +> `Text` and `MutableText` classes that are discussed separately. + +### Actor references with `NativeActorRef` + +As was explained in [safety rules](./safety.md), storing references to actors +inside objects is a bad idea. +Actor boxes and references provide us with a safe way to do that: + +```unrealscript +class MyObject extends AcediaObject; + +var NativeActorRef pawnReference; +// ... + +protected function Finalizer() +{ + _.memory.Free(pawnReference); // This does not destroy stored pawn! + pawnReference = none; +} + +function Pawn GetMyPawn() +{ + if (pawnReference == none) { + return none; + } + return Pawn(pawnReference.Get()); +} + +function SetMyPawn(Pawn newPawn) +{ + if (pawnReference == none) + { + // `UnrealAPI` deals with storing non-Acedia actors such as `Pawn`. + // For `AcediaActor`s you can also use `_.ref.Actor()`. + pawnReference = _.unreal.ActorRef(newPawn); + } + else { + pawnReference.Set(newPawn); + } +} + +function DoWork() +{ + local Pawn myPawn; + myPawn = GetMyPawn(); + if (myPawn == none) { + return; + } + // + // ^ After destroying a pawn, + // `myPawn` local variable might go "bad" and cause crashes, + // so it's a good idea to update it from safe `pawnReference`: + myPawn = GetMyPawn(); + myPawn.health += 10; +} +``` + +Actor boxes do not exist, since we cannot guarantee that value stored inside +them will never change - destroying stored actor will always reset it to `none`. + +### Array boxes and references + +If necessary, box and reference classes can be manually created for any type +of value, including `array<...>`s and `struct`s. +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 +by reference, without copying their entire data every time. +They also provide quite a few several convenience methods. +Here is a list for `FloatArrayRef` as an example: + +| Method | Description | +| ------ | ----------- | +| `Get()` | Returns the whole stored array as `array`. | +| `Set(array)` | Sets the whole array value. | +| `GetItem(int, optional float)` | Returns item at specified index. If index is invalid, returns passed default value. | +| `SetItem(int, float)` |Changes array's value at specified index. | +| `GetLength()` | Returns length of the array. `ref.GetLength()` is faster than `ref.Get().length`, since latter will make a copy of the whole array first | +| `SetLength(int)` | Resizes stored array, doing nothing on negative input. | +| `Empty()` | Empties stored array. | +| `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. | +| `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. | +| `AddItem(float)` | Adds given `float` at the end of the array, expanding it by 1 element. | +| `InsertItem(int, float)` | Inserts given item at index of the array, shifting all the elements starting from `index` one position to the right. | +| `AddArray(array)` / `AddArrayRef(FloatArrayRef)` | Adds given array of items at the end of the array, expanding it by inserted amount. | +| `InsertArray(array)` / `InsertArrayRef(FloatArrayRef)` | Inserts items array at specified index of the array, shifting all the elements starting from `index` by inserted amount to the right. | +| `RemoveItem(float, bool)` | Returns all occurrences of `item` in the caller `float` (optionally only first one). | +| `Find(float)` | Finds first occurrence of specified item in caller `FloatArrayRef` and returns its index. | +| `Replace(float search, float replacement)` | Replaces any occurrence of `search` with `replacement`. | +| `Sort(optional bool descending)` | Sorts array in either ascending or descending order. | + +## [Advanced] Static constructors and finalizers + +Acedia also supports a notion of static constructors and finalizers. + +Static constructor is called for each class only once: + +* Whenever first object of such class is created, + before its constructor is called; +* If you want static initialization to be done earlier, + it is allowed to call static constructor manually: + `class'...'.static.StaticConstructor()`. + +> **NOTE:** Static constructor being called for your class does not guarantee it +> being called for its parent class. They are considered independently. + +Right now relying on static constructors in not advised, but if you are sure +you need them, you can define them like this: + +```unrealscript +public static function StaticConstructor() +{ + // This condition is necessary, DO NOT remove it, leave it AS IS + if (StaticConstructorGuard()) { + return; + } + // Place your logic here + // ... +} +``` + +Static finalizers, however, are more important. +They are called during Acedia's shutdown for any class that had its +static constructor invoked (including for any Acedia class that was allocated). +It can be used to "clean up" after yourself. +To have a clean level change it is important that you undo as many changes to +game's objects as you reasonably can. +It is especially important to reset default values, unless their change is +deliberate. +Here is an example used in the base `AcediaObject` class at some point: + +```unrealscript +protected static function StaticFinalizer() +{ + // Not cleaning object references in `default` values will interfere + // with garbage collection + default._textCache = none; + default._objectPool = none; + // Not cleaning this value will prevent static constructors + // (and a whole bunch of other code) from being called after the map change + default._staticConstructorWasCalled = false; +} +``` + +## [Advanced] Technical details + +### How allocation and deallocation works + +UnrealScript lacks any practical way to destroy objects on demand: +the best one can do is remove any references to the object and wait for +garbage collection. +But garbage collection itself is too slow and causes noticeable lag spikes +for players, making it suitable only for cleaning objects when switching levels. +To alleviate this problem, there exists a standard class `ObjectPool` +that stores unused objects inside dynamic array until they are needed. + +Unfortunately, using a single `ObjectPool` for a large volume of objects is +impractical from performance perspective, since it stores objects of +all classes together and each object allocation from the pool can potentially +require going through the whole array: + +```unrealscript +simulated function Object AllocateObject(class ObjectClass) +{ + local Object Result; + local int ObjectIndex; + + for(ObjectIndex = 0;ObjectIndex < Objects.Length;ObjectIndex++) + { + if(Objects[ObjectIndex].Class == ObjectClass) + { + Result = Objects[ObjectIndex]; + Objects.Remove(ObjectIndex,1); + break; + } + } + + if(Result == None) + Result = new(Outer) ObjectClass; + + return Result; +} +``` + +Acedia uses a separate object pool (implemented by `AcediaObjectPool`) +for every single class, making object allocation as trivial as grabbing +the last stored object from `AcediaObjectPool`'s internal dynamic array: + +```unrealscript +// From `AcediaObjectPool` sources +public final function AcediaObject Fetch() +{ + local AcediaObject result; + if (storedClass == none) return none; + if (objectPool.length <= 0) return none; + + result = objectPool[objectPool.length - 1]; + objectPool.length = objectPool.length - 1; + return result; +} +``` + +New pool is prepared for every class you create, as long as it is inherited +from `AcediaObject`. +`AcediaActor`s do not use object pools and are simply `Destroy()`ed. + +### Detecting deallocated objects + +Deallocated objects are not destroyed, but simply stored inside a special pool +to be later reused. +Problems can arise if some function deallocates your object without telling you. +If you suspect this might be the case or just want to make extra sure +your object is intact, then there are ways to confirm it. + +First relevant method is defined in any class derived from +`AcediaObject` or `AcediaActor`: `IsAllocated()` that returns +`true` for objects that are currently allocated and `false` otherwise. +However, this method is not enough, since your object might be *reallocated*: +first deallocated and then allocated again by some other code. +Then `IsAllocate()` will return `true` even though your reference is +no longer valid. + +This issue can be solved with *life version* - `int` value that changes +each time object is reallocated: + +```unrealscript +local int lifeVersion; +local Text originalObject, newObject; +// Get object and remember its life version +originalObject = _.text.FromString("My string"); +lifeVersion = originalObject.GetLifeVersion(); +// Allocated objects always have positive life version +// and it won't change until they get deallocated +Log(originalObject.IsAllocated()); // true +Log(originalObject.GetLifeVersion() > 0); // true +Log(originalObject.GetLifeVersion() == lifeVersion); // true +// But after deallocation, life version will change and become negative +originalObject.FreeSelf(); +Log(originalObject.IsAllocated()); // false +Log(originalObject.GetLifeVersion() > 0); // false +Log(originalObject.GetLifeVersion() == lifeVersion); // false +// This will reallocate object we've just deallocated +// and it will have different (positive) life version +newObject = _.text.FromString("New string!"); +Log(originalObject == newObject); // true +Log(originalObject.IsAllocated()); // true +Log(originalObject.GetLifeVersion() > 0); // true +Log(originalObject.GetLifeVersion() == lifeVersion); // false +``` + +Summarizing, to detect whether your object was reallocated - +remember its life version value right after allocation +and then compare it to the `GetLifeVersion()`'s result. +Value returned by `GetLifeVersion()` changes after each reallocation +and won't repeat for the same object. +The only guarantee about life versions of deallocated objects is that they will +be negative. + +### Customizing object pools for your classes + +Object pool usage can be disabled completely for your class by setting +`usesObjectPool = false` in `defaultproperties` block. +Without object pools `_.memory.Allocate()` will create a new instance of +your class every single time. + +You can also set a limit to how many objects will be stored in +an object pool with `defaultMaxPoolSize` variable. +Negative number (default for `AcediaObject`) means that object pool can +grow without a limit. +`0` effectively disables object pool, similar to setting +`usesObjectPool = false`. +However, this can be overwritten by server's settings +(see `AcediaSystem.ini: AcediaObjectPool`). diff --git a/docs/safety.md b/docs/safety.md index 6a54d03..d304c71 100644 --- a/docs/safety.md +++ b/docs/safety.md @@ -29,17 +29,18 @@ If you are interested in the explanation of why, you can read discussion This isn't really a problem in most mutators, since they store references to actors (`KFMonster`, `KFPlayerController`, ...) -inside other actors (`Mutator`, `GameType`, ...). -However in Acedia almost everything is a non-actor object, which can cause +inside other actors (`Mutator`, `GameType`, ...); +however, in Acedia almost everything is a non-actor object, which can cause a lot of trouble, since even a simple check like `myActor != none` can lead to a crash. Acedia's goal is to provide you with enough wrapper API, so that you don't have to reference actors directly. We are a long way away from that goal, so for whenever these API are not enough, -Acedia provides a way to work with actors safely. +Acedia provides a way to work with actors safely +(see [Actor references with `NativeActorRef`](./objects.md)). -## Take care to explicitly free unneeded objects, with example of `Text` +## Take care to explicitly free unneeded objects We'll illustrate this point with `Text` - Acedia's own type that is used as a replacement for `string`. Consider following simple code: @@ -93,7 +94,7 @@ function MyFunction() Here `FreeSelf()` call marks `message` as an unneeded object, making it available to be reused. -In fact, if you call `MyFunction()` as we've wrote it several times in a row: +In fact, if you call new `MyFunction()` several times in a row: ```unrealscript MyFunction() @@ -127,10 +128,12 @@ function MyFunction() ``` Deallocating a `message` does not make an actual object go away and, -without setting `message` variable to `none`, you risk continuing to use it. -However some other piece of code might re-allocate that object +without setting `message` variable to `none`, you risk continuing to use it; +however, some other piece of code might re-allocate that object and use it for something completely different. -This means unpredictalbe and undefined behavior for both of you - -a situation that has to be avoided. +This means unpredictable and undefined behavior for everybody. To avoid creating with this problem - everyone must always make sure to *forget* about objects you've deallocated by setting your references to `none`. + +> **NOTE:** This also means that you should not deallocate the same object +> more than once.