Browse Source

Initial commit

master
Anton Tarasenko 2 years ago
commit
4624668be3
  1. 1
      .gitignore
  2. 6
      book.toml
  3. 17
      src/SUMMARY.md
  4. 4
      src/collections/index.md
  5. 74
      src/collections/iterators.md
  6. 53
      src/collections/storing_builtin.md
  7. 123
      src/collections/using_json.md
  8. 43
      src/events/connecting.md
  9. 163
      src/events/custom.md
  10. 20
      src/events/disconnecting.md
  11. 4
      src/events/index.md
  12. 155
      src/feature/creating_feature.md
  13. 18
      src/feature/index.md
  14. 42
      src/feature/packaging_feature.md
  15. 11
      src/object/allocate.md
  16. 50
      src/object/detect_reallocation.md
  17. 10
      src/object/force_deallocation.md
  18. 9
      src/object/index.md

1
.gitignore vendored

@ -0,0 +1 @@
book

6
book.toml

@ -0,0 +1,6 @@
[book]
authors = ["Anton Tarasenko"]
language = "en"
multilingual = false
src = "src"
title = "Lazy cookbook"

17
src/SUMMARY.md

@ -0,0 +1,17 @@
# Summary
- [Collections recipes](./collections/index.md)
- [Storing built-in values](./collections/storing_builtin.md)
- [Using iterators](./collections/iterators.md)
- [Using JSON data](./collections/using_json.md)
- [Acedia events system](./events/index.md)
- [Connecting to signal](./events/connecting.md)
- [Disconnecting from signal](./events/disconnecting.md)
- [Custom signals](./events/custom.md)
- [Feature recipes](./feature/index.md)
- [Creating a `Feature`](./feature/creating_feature.md)
- [Packaging a `Feature`](./feature/packaging_feature.md)
- [AcediaObject recipes](./object/index.md)
- [Allocating object](./object/allocate.md)
- [Detect object reallocation](./object/detect_reallocation.md)
- [Force object deallocation](./object/force_deallocation.md)

4
src/collections/index.md

@ -0,0 +1,4 @@
# Collections recipes
A collection of `how-to`s for working with Acedia's collections:
`ArrayList` and `HashTable` classes.

74
src/collections/iterators.md

@ -0,0 +1,74 @@
# Using iterators
## Simple iteration
For both `ArrayList` and `HashTable`:
```unrealscript
function ListKeyValue(Collection data)
{
local AcediaObject key, value;
local CollectionIterator iter;
for (iter = data.Iterate(); !iter.HasFinished(); iter.Next())
{
key = iter.GetKey();
value = iter.Get();
// Do what you want here
_.memory.Free(key);
_.memory.Free(value);
}
iter.FreeSelf();
}
```
## Skipping `none` values
```unrealscript
function ListKeyValue(Collection data)
{
local AcediaObject key, value;
local CollectionIterator iter;
iter = data.Iterate().LeaveOnlyNotNone();
while (!iter.HasFinished())
{
key = iter.GetKey();
value = iter.Get();
// Do what you want here
_.memory.Free(key);
_.memory.Free(value);
iter.Next();
}
iter.FreeSelf();
}
```
## Iteration over `Text` keys only
Text keys are only relevant for `HashTable`s:
```unrealscript
function ListKeyValue(HashTable data)
{
local AcediaObject key, value;
local Text textKey;
local CollectionIterator iter;
for (iter = data.Iterate(); !iter.HasFinished(); iter.Next())
{
key = iter.GetKey();
value = iter.Get();
textKey = Text(key);
if (textKey != none)
{
Log(textKey.ToString() $ ":"
@ _.text.IntoString(_.json.Print(value)));
}
_.memory.Free(key);
_.memory.Free(value);
// `textKey` is the same reference as `key`!
}
iter.FreeSelf();
}
```

53
src/collections/storing_builtin.md

@ -0,0 +1,53 @@
# Storing built-in values
## Storing in the array
`ArrayList` serves the role of the regular array and storing built-in value data
inside it is simple:
```unrealscript
local ArrayList data;
data = _.collections.EmptyArrayList();
// Add as the last element
data.AddFloat(-2.5);
// Set at a particular index (length will be auto-adjusted)
data.Set(2, "just a string");
Log("data[0] =" @ data.GetFloat(0)); // data[0] = -2.5
Log("data[1] =" @ data.IsNone(1)); // data[1] = true
Log("data[2] =" @ data.GetString(2)); // data[2] = just a string
```
## Getting `string` as a `Text`
By default Acedia's collections use `Text` to store `string`s, so we can also
get their values as `Text`:
```unrealscript
local Text textInstance;
local ArrayList data;
data = _.collections.EmptyArrayList();
data.SetString(0, "Hello, world!");
textInstance = data.GetText(0);
Log("data[0] =" @ textInstance.ToString());
// Same as any object returned by a function, `textInstance` must be released
textInstance.FreeSelf();
```
## Storing values by keys
For storing values using keys instead of numeric indices, the Acedia's way is to
use `HashTable`:
```unrealscript
local HashTable data;
data = _.collections.EmptyHashTable();
data.SetBool(P("Deal damage?"), true);
data.SetInt(P("Damage amount"), 9001);
Log("Deal damage?" @ data.GetFloat(P("Deal damage?"))); // Deal damage? true
Log("Damage amount:" @ data.IsNone(1)); // Damage amount: 9001
```

123
src/collections/using_json.md

@ -0,0 +1,123 @@
# Using JSON data
JSON data can be stored inside Acedia's collections using only built-in values
types and `ArrayList`/`HashTable` collections:
* JSON's *null* can be stored as `none`;
* JSON's *true*/*false* can be stored as `bool`'
* JSON's *number* can be stored as either `int` or `float` (actually Acedia also
contains `BigInt` type for storing arbitrarily large integer values, but it
isn't yet implemented into JSON parsing);
* JSON's *string* can be stored as `string`/`Text`/`MutableText`;
* JSON's *array* can be stored as an `ArrayList`;
* JSON's *object* can be stored as `HashTable` with `Text` keys.
> **NOTE:** JSON *does not* have a separate undefined type and Acedia uses
> `none` instead of the missing values.
## Converting JSON into Acedia collections
### Parsing JSON input
To get an Acedia collection from a JSON object like this one:
```json
{
"innerObject": {
"my_bool": true,
"array": [
"Engine.Actor",
false,
null,
{
"something \"here\"": "yes",
"maybe": 0.003
},
56.6
],
"one more": {
"nope": 324532,
"whatever": false,
"o rly?": "ya rly"
},
"my_int": -9823452
},
"some_var": -7.32,
"another_var": "aye!"
}
```
recorded in the `string` named `jsonData` you can do the following:
```unrealscript
local Parser parser;
local HashTable jsonObject;
parser = _.text.ParseString(jsonData);
jsonObject = HashTable(_.json.ParseWith(parser));
if (!parser.Ok()) {
// Handle errors
}
else if (!parser.Skip().HasFinished()) {
// There is more input left after parsing JSON value - is this a problem?
}
// '/innerObject/array/3/maybe' is 0.003
Log("'/innerObject/array/3/maybe' is"
@ jsonObject.GetFloatBy(P("/innerObject/array/3/maybe")));
```
### Constructing by hand
Example of constructing the same object by hand:
```unrealscript
local HashTable jsonObject;
local HashTable innerObject, oneMore, anonymousObject;
local ArrayList jsonArray;
anonymousObject = _.collections.EmptyHashTable();
anonymousObject.SetString(P("something \"here\""), "yes");
anonymousObject.SetFloat(P("maybe"), 0.003);
jsonArray = _.collections.EmptyArrayList()
.AddString("Engine.Actor")
.jsonArray.AddBool(false)
.jsonArray.AddItem(none)
.jsonArray.AddItem(anonymousObject)
.jsonArray.AddFloat(56.6);
oneMore = _.collections.EmptyHashTable()
.SetString(P("o rly?"), "ya rly")
.SetFloat(P("nope"), 324532)
.SetBool(P("whatever"), false);
innerObject = _.collections.EmptyHashTable()
.SetItem(P("array"), jsonArray)
.SetItem(P("one more"), oneMore)
.SetBool(P("my_bool"), true)
.SetInt(P("my_int"), -9823452);
// Put it all together!
jsonObject = _.collections.EmptyHashTable()
.SetItem(P("innerObject"), innerObject)
.SetFloat(P("some_var"), -7.32)
.SetString(P("another_var"), "aye!");
// If you only want to keep `jsonObject`, release other references -
// they won't disappear
anonymousObject.FreeSelf();
jsonArray.FreeSelf();
oneMore.FreeSelf();
innerObject.FreeSelf();
```
## Converting Acedia collections into JSON
Use `_.json.Print()` to get a compact JSON representation:
```unrealscript
// {"innerObject":{"my_bool":true,"array":["Engine.Actor",false,null,{"something \"here\"":"yes","maybe":0.003},56.6],"one more":{"nope":324532,"whatever":false,"o rly?":"ya rly"},"my_int":-9823452},"some_var":-7.32,"another_var":"aye!"}
Log(_.text.IntoString(_.json.Print(jsonObject)));
```
or `_.json.PrettyPrint()` for a nice-looking (multiline, indented and colored)
result.

43
src/events/connecting.md

@ -0,0 +1,43 @@
# Connecting to signal
## From inside `AcediaObject` (or its child class)
Supposing you want to connect to a signal function
`_server.unreal.gameRules.OnNetDamage()`:
```unrealscript
_server.unreal.gameRules.OnNetDamage(self).connect = handler;
```
where `handler()` can be any function with appropriate signature:
```unrealscript
function int Handler(
int originalDamage,
int damage,
Pawn injured,
Pawn instigator,
Vector hitLocation,
out Vector momentum,
class<DamageType> damageType)
{
// Do whatever
return damage;
}
```
## From inside non-`AcediaObject`
Pass any other `AcediaObject` object as an argument to your signal function.
For example:
```unrealscript
local ServiceAnchor receiver;
receiver = ServiceAnchor(_.memory.Allocate(class'ServiceAnchor'));
_server.unreal.gameRules.OnNetDamage(receiver).connect = handler;
```
> **NOTE:** Signal *does not* keep the reference to passed argument and once
> `receiver` gets deallocated - signal will stop notifying handlers connected
> through it.

163
src/events/custom.md

@ -0,0 +1,163 @@
# Custom signals
## Simple notification events
If you need to add an event with handlers that don't take any parameters and
don't return anything, then easiest way it to use `SimpleSignal` / `SingleSlot`
classes:
```unrealscript
class MyEventClass extends AcediaObject;
var private SimpleSignal onMyEventSignal;
protected function Constructor()
{
onMyEventSignal = SimpleSignal(_.memory.Allocate(class'SimpleSignal'));
}
protected function Finalizer()
{
_.memory.Free(onMyEventSignal);
onMyEventSignal = none;
}
public function SimpleSlot OnMyEvent(AcediaObject receiver)
{
return SimpleSlot(onMyEventSignal.NewSlot(receiver));
}
// Suppose you want to emit the `signal` when this function is called...
public function SimpleSlot FireOffMyEvent(AcediaObject receiver)
{
// ...simply call this and all the slots will have their handlers called
onMyEventSignal.Emit();
}
```
Then you can use `OnMyEvent()` as a *signal function*:
```unrealscript
// To add handlers
myEventClassInstance.OnMyEvent(self).connect = handler;
// To remove handlers
myEventClassInstance.OnMyEvent(self).Disconnect();
```
## Events with parameters
Some of the events, like `OnNetDamage()`, can take
parameters.
To create signals like that, follow the template and define new classes like so:
```unrealscript
class MySignal extends Signal;
public final function Emit(<PARAMETERS>)
{
local Slot nextSlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
MySlot(nextSlot).connect(<PARAMETERS>);
nextSlot = GetNextSlot();
}
CleanEmptySlots();
}
defaultproperties
{
relatedSlotClass = class'MySlot'
}
```
```unrealscript
class MySlot extends Slot;
delegate connect(<PARAMETERS>)
{
DummyCall(); // This allows Acedia to cleanup slots without set handlers
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
defaultproperties
{
}
```
Here you can use any set of parameters instead of `<PARAMETERS>`.
## Events with return values
Sometimes you want your handlers to respond in some way to the event.
You can either allow them to modify input parameters (e.g. by declaring them as
`out`) or allow them to have return value.
`OnNetDamage()`, for example, is allowed to modify incoming damage by returning
a new value.
To add `signal`s / `slot`s that handle return value use following templates:
```unrealscript
class MySignal extends Signal;
public final function <RETURN_TYPE> Emit(<PARAMETERS>)
{
local <RETURN_TYPE> newValue;
local Slot nextSlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
newValue = <SLOT_CLASS>(nextSlot).connect(<PARAMETERS>);
// This check is necessary before using returned value
if (!nextSlot.IsEmpty())
{
// Now handle `newValue` however you see fit
}
nextSlot = GetNextSlot();
}
CleanEmptySlots();
// Return whatever you see fit after handling all the slots
return <END_RETURN_VALUE>;
}
defaultproperties
{
relatedSlotClass = class'MySlot'
}
```
```unrealscript
class MySlot extends Slot;
delegate <RETURN_TYPE> connect(<PARAMETERS>)
{
DummyCall();
// Return anything you want:
// this value will be filtered inside corresponding `Signal`
// if no handler is set to the associated slot
return <???>;
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
```

20
src/events/disconnecting.md

@ -0,0 +1,20 @@
# Disconnecting from signal
## From inside `AcediaObject` (or its child class)
Supposing you want to disconnect from a signal function
`_server.unreal.gameRules.OnNetDamage()`:
```unrealscript
_server.unreal.gameRules.OnNetDamage(self).Disconnect();
```
## From inside non-`AcediaObject`
To do that you must have passed another `AcediaObject` as an argument to
`OnNetDamage()` (or any other signal).
Supposing that object was called `receiver`:
```unrealscript
_server.unreal.gameRules.OnNetDamage(receiver).Disconnect();
```

4
src/events/index.md

@ -0,0 +1,4 @@
# Acedia events system
A collection of `how-to`s for working with Acedia's event system: `Signal`s and
`Slot`s.

155
src/feature/creating_feature.md

@ -0,0 +1,155 @@
# Creating a Feature
## Prepare classes
When creating a `Mutator` you must create a child `Mutator` class, but
when creating a `Feature` you must create two classes: one for the `Feature`
itself and one for its config.
Name your config class something human-readable and your `Feature` class should
have the same name, but with '_Feature' suffix.
For example, if you want your configs to be stored inside 'MyFeatureConfig.ini',
bare minimum class skeletons are:
```unrealscript
// MyFeature.uc
class MyFeature extends FeatureConfig
perobjectconfig
config(MyFeatureConfig);
defaultproperties
{
configName = "MyFeatureConfig"
}
```
```unrealscript
// MyFeature_Feature.uc
class MyFeature_Feature extends Feature;
defaultproperties
{
configClass = class'MyFeature'
}
```
## Setup config variables in config file
Config variables are meant to be added into your `FeatureConfig` class.
Assuming you want to add `bool` and `int` settings, simply add two public config
variables:
```unrealscript
// MyFeature.uc
class MyFeature extends FeatureConfig
perobjectconfig
config(MyFeatureConfig);
var public config int levelOfAwesome;
var public config bool enableTrollEngine;
defaultproperties
{
configName = "MyFeatureConfig"
}
```
To setup their default values, instead of `defaultproperties`
define `DefaultIt()` method:
```unrealscript
// MyFeature.uc
protected function DefaultIt()
{
levelOfAwesome = 11;
enableTrollEngine = true;
}
```
### Conversion to/from `HashTable`
To make full use out of your `Feature`, Acedia also requires that you provide
methods to convert to and from its collection.
For our example simply add two more methods:
```unrealscript
// MyFeature.uc
protected function HashTable ToData()
{
local HashTable data;
data = __().collections.EmptyHashTable();
data.SetInt(P("levelOfAwesome"), levelOfAwesome);
data.SetBool(P("enableTrollEngine"), enableTrollEngine);
return data;
}
protected function FromData(HashTable source)
{
if (source == none) {
return;
}
// Second parameters should are fallback - use values from `DefaultIt()`
levelOfAwesome = source.GetInt(P("levelOfAwesome"), 11);
enableTrollEngine = source.GetBool(P("enableTrollEngine"), true);
}
```
## Setup your feature to use your config
Recommended way to do it is to duplicate your config variables in your
`Feature` class:
```unrealscript
// MyFeature_Feature.uc
class MyFeature_Feature extends Feature;
// Not actually config variables
var public /*config*/ int levelOfAwesome;
var public /*config*/ bool enableTrollEngine;
defaultproperties
{
configClass = class'MyFeature'
}
```
Then add code that will handle setting config data for your `Feature`:
```unrealscript
// MyFeature_Feature.uc
protected function SwapConfig(FeatureConfig config)
{
local MyFeature newConfig;
newConfig = MyFeature(config);
if (newConfig == none) {
return;
}
levelOfAwesome = newConfig.levelOfAwesome;
enableTrollEngine = enableTrollEngine.disableTick;
// Here you can also add any logic that needs to be performed when
// values of the config variables were swapped mid-game, if you care to
// support it.
// Just beware that this method can be called when you `Feature` is
// both enabled and not (can be checked with `IsEnabled()`).
}
```
## Run initialization / shutdown logic
Instead of normal Acedia's `Constructor()` or `Finalizer()`, for `Featuire`s
one should use:
```unrealscript
protected function OnEnabled()
{
}
protected function OnDisabled()
{
}
```
These methods are called when feature gets enabled or disabled, which can,
in theory, happen several times per game.
They aren't called when `Feature`'s config is changed mid-game, so sometimes
it is convenient to move part of the initialization logic into `SwapConfig()`.

18
src/feature/index.md

@ -0,0 +1,18 @@
# Feature recipes
A collection of `how-to`s for working with `Feature` class.
This class is Acedia's replacement for `Mutators`: a certain subset of
functionality that can be enabled or disabled, according to server owner's
wishes.
Unlike `Mutator`s:
* There is no limit for the amount of `Feature`s that can be active
at the same time;
* They also provide built-in ability to have several different configs
that can be swapped during the runtime;
* They can be enabled / disabled during the runtime.
Achieving these points currently comes at the cost of developer having to
perform additional work;
* They are server-side and for now are not supposed to be created for
the clients.

42
src/feature/packaging_feature.md

@ -0,0 +1,42 @@
# Packaging a Feature
Suppose you've created your own `Feature` class and want to compile it to be
usable with Acedia.
In this how-to we will assume that your class is named `MyFeature_Feature`
(same as [here](./creating_feature.md)).
## Create regular UnrealScript mod
First step is still to prepare compilation of `*.u` file, like one would do for
any regular UnrealScript mod and put your `*.uc` script files inside.
## Create manifest
To know what is available in each package, Acedia reads its manifest.
To add a manifest create a file named exactly 'Manifest.uc' in your package:
```unrealscript
// Manifest.uc
class Manifest extends _manifest
abstract;
defaultproperties
{
}
```
## Register feature in the manifest
To add your `Feature` into manifest, simply fill it inside `features` array:
```unrealscript
// Manifest.uc
class Manifest extends _manifest
abstract;
defaultproperties
{
// You can add any amount of features from a single package here
features(0) = class'MyFeature_Feature'
}
```

11
src/object/allocate.md

@ -0,0 +1,11 @@
# Allocating object
If you've created a new child class (say, `MyCoolObject`) of `AcediaObject` and
want to allocate it, simply do:
```unrealscript
local MyCoolObject instance;
instance = MyCoolObject(_.memory.Allocate(class'MyCoolObject'));
```
It is guaranteed to succeed for non-abstract classes.

50
src/object/detect_reallocation.md

@ -0,0 +1,50 @@
# Detect object reallocation
Sometimes you want to keep track of an object without declaring that you own its
reference with `NewRef()`, thus allowing it to get deallocated even while you
are keeping a reference to it.
How to do it in a safe way and detect that it was deallocated/reallocated?
## What to do when you start storing a reference
When you first get an instance of `AcediaObject` you'd like to store in such
a way, check if it's allocated and remember its life version:
```unrealscript
var int storedReferenceLifeVersion;
var AcediaObject storedReference;
function Store(AcediaObject newReference)
{
if (newReference == none) return;
if (!newReference.IsAllocated()) return;
storedReference = newReference;
storedReferenceLifeVersion = newReference.GetLifeVersion();
// Not calling `newReference.NewRef()`, so `newReference` can get
// deallocated at any time!
}
```
## What to do when you want to check if stored reference was deallocated
Before using such reference again you simply need to check if life version has
changed.
If it did, then this instance was reallocated and repurposed and you shouldn't
use it anymore.
Othewrwise it is still the same object.
```unrealscript
function AcediaObject Get()
{
if (storedReference == none) {
return none;
}
if (storedReference.GetLifeVersion() != storedReferenceLifeVersion)
{
// This object was reallocated! Time to forget about it.
storedReference = none;
}
return storedReference;
}
```

10
src/object/force_deallocation.md

@ -0,0 +1,10 @@
# Forcing object deallocation
`self.FreeSelf()` and `_.memory.Free()` only reduce reference counter of
the object.
But how to forcefully deallocate it?
**Just do not do that you fucking idiot**.
If you need to somehow dispose of what that object represents, simply add
a flag that marks that object as disposed.

9
src/object/index.md

@ -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…
Cancel
Save