Skip to content

Commit 44489f8

Browse files
committed
Reclaim unused states
1 parent e7f351b commit 44489f8

File tree

11 files changed

+65
-30
lines changed

11 files changed

+65
-30
lines changed

Documentation/Design notes.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# XGOAP design notes
2+
3+
## The `Clonable` interface
4+
5+
This interface is for users to provide their own clone implementation; necessary because deep clones via serialization are very slow.
6+
7+
`Clone()` receives an instance of the model; **user must override all fields** because the instance (by design) may contain dirty state (To avoid allocation overheads, instances of the model are reused whenever possible - even when pooling is disabled)
8+
9+
When implementing `Allocate()`, call a no-arg constructor or similar. This interface is required because `new`ing generic types directly is (in the author's experience) surprisingly slow (also read [here](http://www.philosophicalgeek.com/2012/04/02/activator-createinstance-slow-vs-less-slow/))

Documentation/Design notes.md.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/Clonable.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
namespace Activ.GOAP{
2-
public interface Clonable{
2+
public interface Clonable<T>{
33

4-
object Clone();
4+
T Allocate ();
55

6-
}
7-
}
6+
T Clone (T storage);
7+
8+
}}

Runtime/Details/ActionHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace Activ.GOAP{
22
public interface ActionHandler{
33

4-
void Effect<T>(object action, GameAI<T> client);
4+
void Effect<T>(object action, GameAI<T> client) where T: class;
55

66
}}

Runtime/Details/ActionMap.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ int frameCount{get{
2323
void ActionHandler.Effect<T>(object action, GameAI<T> client){
2424
switch(action){
2525
case string noArg:
26-
if(noArg == Solver<Agent>.INIT) return;
26+
// TODO should be Solver.INIT but move that constant
27+
// somewhere else first
28+
if(noArg == "%init") return;
2729
Print($"No-arg: {noArg} @{frameCount}");
2830
Map(noArg, client).Invoke(client, NoArg);
2931
return;
@@ -39,7 +41,7 @@ void ActionHandler.Effect<T>(object action, GameAI<T> client){
3941
}
4042
}
4143

42-
MethodInfo Map<T>(string name, GameAI<T> client){
44+
MethodInfo Map<T>(string name, GameAI<T> client) where T: class{
4345
map = map ?? new Dictionary<string, MethodInfo>();
4446
MethodInfo method;
4547
map.TryGetValue(name, out method);

Runtime/Details/NodeSet.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public NodeSet(T x, Func<T, float> h, bool sorted = true,
1919
this.capacity = capacity;
2020
this.precision = precision;
2121
states.Add(Assert(x, "Initial state"));
22-
list.Add(new Node<T>(Solver<T>.INIT, x));
22+
// TODO INIT const
23+
list.Add(new Node<T>("%init", x));
2324
}
2425

2526
public bool capacityExceeded => count > capacity;
@@ -29,8 +30,8 @@ public static implicit operator bool(NodeSet<T> self)
2930

3031
internal int count => list.Count;
3132

32-
public void Insert(Node<T> n){
33-
if(!states.Add(n.state)) return;
33+
public bool Insert(Node<T> n){
34+
if(!states.Add(n.state)) return false;
3435
if(sorted){
3536
n.value = n.cost + (h != null ? h(n.state) : 0);
3637
if(precision > 0) n.value = (int)(n.value / precision);
@@ -47,10 +48,10 @@ public void Insert(Node<T> n){
4748
for(int i = list.Count-1; i >= 0; i--){
4849
if(n.value <= list[i].value){
4950
list.Insert(i + 1, n);
50-
return;
51+
return true;
5152
}
5253
}
53-
} list.Insert(0, n);
54+
} list.Insert(0, n); return true;
5455
}
5556

5657
public Node<T> Pop(){

Runtime/GameAI.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ one component.
1515
namespace Activ.GOAP{
1616
// NOTE: derives from MonoBehaviour when used with Unity3D
1717
// (see Runtime/Unity/GameAI.cs)
18-
public abstract partial class GameAI<T> : SolverOwner{
18+
public abstract partial class GameAI<T> : SolverOwner
19+
where T : class {
1920

2021
public bool verbose;
2122
public Solver<T> solver;

Runtime/Solver.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using S = Activ.GOAP.PlanningState;
66

77
namespace Activ.GOAP{
8-
public class Solver<T> : SolverStats{
8+
public class Solver<T> : SolverStats where T : class{
99

1010
public const string INIT = "%init";
1111
const string ZERO_COST_ERR = "Zero cost op is not allowed",
@@ -19,6 +19,7 @@ public class Solver<T> : SolverStats{
1919
public int fxMaxNodes { get; private set; }
2020
public int I { get; private set; }
2121
//
22+
T storage;
2223
T initialState;
2324
Goal<T> goal;
2425
NodeSet<T> avail = null;
@@ -71,7 +72,8 @@ void ExpandActions(Node<T> x, NodeSet<T> @out){
7172
if(!brfs && (r.cost <= 0))
7273
throw new Ex(ZERO_COST_ERR);
7374
var name = p.Actions()[i].Method.Name;
74-
@out.Insert(new Node<T>(name, y, x, r.cost));
75+
if(@out.Insert(new Node<T>(name, y, x, r.cost)))
76+
storage = null;
7577
}
7678
}
7779
}
@@ -87,12 +89,14 @@ void ExpandMethods(Node<T> x, NodeSet<T> @out){
8789
if(!brfs && r.cost <= 0)
8890
throw new Ex(ZERO_COST_ERR);
8991
var effect = p.Functions()[i].effect;
90-
@out.Insert(new Node<T>(effect, y, x, r.cost));
92+
if(@out.Insert(new Node<T>(effect, y, x, r.cost)))
93+
storage = null;
9194
}
9295
}
9396
}
9497

95-
internal static T Clone(T x)
96-
=> (x is Clonable c) ? (T)c.Clone() : CloneUtil.DeepClone(x);
98+
internal T Clone(T x) => (x is Clonable<T> c)
99+
? c.Clone(storage = storage ?? c.Allocate())
100+
: CloneUtil.DeepClone(x);
97101

98102
}}

Tests/Editor/ActionMapTest.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class ActionMapTest : TestBase{
1111
[Test] public void Effect_with_invalid_type()
1212
=> Assert.Throws<ArgumentException>(
1313
() => ((ActionHandler)x).Effect(
14-
0, (GameAI<Agent>)null) );
14+
0, (GameAI<T>)null) );
1515

1616
[Test] public void Print_verbose(){
1717
x.verbose = true;
@@ -23,4 +23,6 @@ [Test] public void Print_non_verbose(){
2323
x.Print("");
2424
}
2525

26+
class T{ public T(){} }
27+
2628
}}

Tests/Editor/NodeSetTest.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,48 @@
11
using System; using NUnit.Framework;
2-
using static Activ.GOAP.Solver<Activ.GOAP.Agent>;
2+
using static Activ.GOAP.Solver<Activ.GOAP.Idler>;
33

44
namespace Activ.GOAP{
55
public class NodeSetTest : TestBase{
66

7-
NodeSet<Agent> x;
7+
NodeSet<Idler> x;
88
Idler idler = new Idler();
99

1010
[SetUp] public void Setup()
11-
=> x = new NodeSet<Agent>(idler, null);
11+
=> x = new NodeSet<Idler>(idler, null);
1212

1313
[Test] public void InitStateMustExist ()
1414
=> Assert.Throws<NullReferenceException>(
15-
() => new NodeSet<Agent>(null, null));
15+
() => new NodeSet<Idler>(null, null));
1616

1717
[Test] public void TrueWithinCapacity()
1818
{ if(x){ } else Assert.Fail(); }
1919

2020
[Test] public void FalseOverCapacity()
21-
{ o((bool)new NodeSet<Agent>(idler, null, capacity: 0), false); }
21+
{ o((bool)new NodeSet<Idler>(idler, null, capacity: 0), false); }
2222

2323
[Test] public void FalseWhenEmpty()
2424
{ x.Pop(); o( (bool)x, false); }
2525

2626
[Test] public void InsertAndSkipExisting()
27-
{ x.Insert(new Node<Agent>("x", idler)); o( x.count, 1); }
27+
{ x.Insert(new Node<Idler>("x", idler)); o( x.count, 1); }
2828

2929
[Test] public void InsertUnsorted(){
30-
x.sorted = false;
31-
x.Insert(new Node<Agent>("x", new Inc())); o( x.count, 2);
30+
var z = new NodeSet<T>(new T(), null);
31+
z.sorted = false;
32+
z.Insert(new Node<T>("x", new T())); o( z.count, 2);
3233
}
3334

34-
[Test] public void InsertAndSort()
35-
{ x.Insert(new Node<Agent>("x", new Inc())); o( x.count, 2); }
35+
[Test] public void InsertAndSort(){
36+
var z = new NodeSet<T>(new T(), null);
37+
z.Insert(new Node<T>("x", new T())); o( z.count, 2); }
3638

3739
[Test] public void Pop(){
3840
var z = x.Pop(); o( x.count, 0 ); o( z.state is Idler );
3941
o( z.action, INIT);
4042
}
4143

44+
class T{
45+
int x = 0; public T() {} public T(int x){ this.x = x; }
46+
}
47+
4248
}}

0 commit comments

Comments
 (0)