Skip to content

Commit 56e4dd0

Browse files
committed
Add atom pooling
1 parent 6f3b88a commit 56e4dd0

12 files changed

+205
-28
lines changed

CodeGen/AtomWeaverV2.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ internal class AtomWeaverV2
1717
private const string ConstructorName = ".ctor";
1818
private const string DirectEvaluateMethodName = nameof(ComputedAtom<int>.DirectEvaluate);
1919
private const string CompAndInvalidateMethodName = nameof(ComputedAtom<int>.CompareAndInvalidate);
20-
private const string CreateAtomMethodName = nameof(CodeGenAtom.Create);
20+
private const string CreateAtomMethodName = nameof(CodeGenAtom.CreatePooled);
21+
private const string ThrowIfDisposedMethodName = nameof(LifetimeScopeExtension.ThrowIfDisposed);
2122
private const string KeepAliveParameterName = nameof(AtomAttribute.KeepAlive);
2223

2324
private List<DiagnosticMessage> _diagnosticMessages = new List<DiagnosticMessage>();
@@ -31,6 +32,7 @@ internal class AtomWeaverV2
3132
private MethodReference _atomGetValueMethod;
3233
private MethodReference _atomDirectEvalMethod;
3334
private MethodReference _atomCompAndInvalidateMethod;
35+
private MethodReference _throwIfDisposedMethod;
3436

3537
private MethodReference _atomPullCtorMethod;
3638

@@ -69,6 +71,7 @@ private void Prepare(AssemblyDefinition assembly)
6971
var atomTypeDef = _atomType.Resolve();
7072
var atomPullDef = _module.ImportReference(typeof(Func<>)).Resolve();
7173
var atomFactoryDef = _module.ImportReference(typeof(CodeGenAtom)).Resolve();
74+
var lifetimeScopeExtensionsDef = _module.ImportReference(typeof(LifetimeScopeExtension)).Resolve();
7275

7376
_atomGetValueMethod = _module.ImportReference(atomTypeDef.FindProperty(ValuePropertyName).GetMethod);
7477

@@ -78,6 +81,8 @@ private void Prepare(AssemblyDefinition assembly)
7881
_module.ImportReference(atomTypeDef.FindMethod(CompAndInvalidateMethodName, 1));
7982

8083
_atomPullCtorMethod = _module.ImportReference(atomPullDef.FindMethod(ConstructorName, 2));
84+
_throwIfDisposedMethod =
85+
_module.ImportReference(lifetimeScopeExtensionsDef.FindMethod(ThrowIfDisposedMethodName, 1));
8186
}
8287

8388
public bool Weave(PropertyDefinition property)
@@ -123,12 +128,12 @@ public bool Weave(PropertyDefinition property)
123128
_diagnosticMessages.Add(UserError.CannotUseAtomAttributeOnAbstractProperty(property));
124129
return false;
125130
}
126-
131+
127132
var atomOptions = new AtomOptions
128133
{
129134
KeepAlive = atomAttribute.GetArgumentValueOrDefault(KeepAliveParameterName, false),
130135
};
131-
136+
132137
property.CustomAttributes.Remove(atomAttribute);
133138

134139
FixAutoPropertyBackingField(property);
@@ -167,7 +172,7 @@ private FieldDefinition CreateAtomField(PropertyDefinition property)
167172
var atomFieldType = Helpers.MakeGenericType(_atomType, property.PropertyType);
168173
return new FieldDefinition(name, FieldAttributes.Private, atomFieldType);
169174
}
170-
175+
171176
private struct AtomOptions
172177
{
173178
public bool KeepAlive;
@@ -190,6 +195,7 @@ private struct AtomGetterMethodWeaver
190195
private MethodReference _atomPullCtorMethod;
191196
private MethodReference _tryEnterMethod;
192197
private MethodReference _atomGetMethod;
198+
private MethodReference _throwIfDisposedMethod;
193199

194200
public AtomGetterMethodWeaver(AtomWeaverV2 weaver, PropertyDefinition property, FieldReference atomField,
195201
AtomOptions options)
@@ -212,6 +218,7 @@ public AtomGetterMethodWeaver(AtomWeaverV2 weaver, PropertyDefinition property,
212218
_atomPullCtorMethod = Helpers.MakeHostInstanceGeneric(weaver._atomPullCtorMethod, propertyType);
213219
_tryEnterMethod = Helpers.MakeHostInstanceGeneric(weaver._atomDirectEvalMethod, propertyType);
214220
_atomGetMethod = Helpers.MakeHostInstanceGeneric(weaver._atomGetValueMethod, propertyType);
221+
_throwIfDisposedMethod = weaver._throwIfDisposedMethod;
215222

216223
var body = property.GetMethod.Body;
217224
body.InitLocals = true;
@@ -247,6 +254,10 @@ public void Weave()
247254

248255
private void Prepend(ref int ind, IList<Instruction> il)
249256
{
257+
// LifetimeScopeExtension.ThrowIfDisposed(this);
258+
il.Insert(ind++, Instruction.Create(OpCodes.Ldarg_0));
259+
il.Insert(ind++, Instruction.Create(OpCodes.Call, _throwIfDisposedMethod));
260+
250261
// if (atom != null) goto nullCheckEnd;
251262
il.Insert(ind++, Instruction.Create(OpCodes.Nop));
252263
il.Insert(ind++, Instruction.Create(OpCodes.Ldarg_0));
@@ -308,6 +319,7 @@ private struct AtomSetterMethodWeaver
308319
private Instruction _preReturnInstruction;
309320

310321
private MethodReference _compAndInvalidateMethod;
322+
private MethodReference _throwIfDisposedMethod;
311323

312324
public AtomSetterMethodWeaver(AtomWeaverV2 weaver, PropertyDefinition property, FieldReference atomField)
313325
{
@@ -320,6 +332,7 @@ public AtomSetterMethodWeaver(AtomWeaverV2 weaver, PropertyDefinition property,
320332
var propertyType = property.PropertyType;
321333
_compAndInvalidateMethod =
322334
Helpers.MakeHostInstanceGeneric(weaver._atomCompAndInvalidateMethod, propertyType);
335+
_throwIfDisposedMethod = weaver._throwIfDisposedMethod;
323336
}
324337

325338
public void Weave()
@@ -336,6 +349,10 @@ public void Weave()
336349

337350
private void Prepend(ref int ind, IList<Instruction> il)
338351
{
352+
// LifetimeScopeExtension.ThrowIfDisposed(this);
353+
il.Insert(ind++, Instruction.Create(OpCodes.Ldarg_0));
354+
il.Insert(ind++, Instruction.Create(OpCodes.Call, _throwIfDisposedMethod));
355+
339356
// if (atom == null) goto nullCheckEnd;
340357
il.Insert(ind++, Instruction.Create(OpCodes.Nop));
341358
il.Insert(ind++, Instruction.Create(OpCodes.Ldarg_0));

Runtime/Atom.Reaction.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public static Reaction Reaction(
3131
Action<Exception> exceptionHandler = null,
3232
string debugName = null)
3333
{
34-
var atom = new ReactionAtom(lifetime, debugName, reaction, exceptionHandler);
34+
var atom = new ReactionAtom(debugName, reaction, exceptionHandler);
35+
lifetime.Register(atom);
3536
atom.Activate();
3637
return atom;
3738
}

Runtime/Atom.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ public static partial class Atom
2626
/// </example>
2727
public static MutableAtom<T> Value<T>(Lifetime lifetime, T value, string debugName = null)
2828
{
29-
return new ValueAtom<T>(lifetime, debugName, value);
29+
var atom = new ValueAtom<T>(debugName, value);
30+
lifetime.Register(atom);
31+
return atom;
3032
}
3133

3234
/// <summary>
@@ -81,7 +83,9 @@ public static MutableAtom<T> Value<T>(T value, string debugName = null)
8183
public static Atom<T> Computed<T>(Lifetime lifetime, Func<T> pull,
8284
bool keepAlive = false, string debugName = null)
8385
{
84-
return new ComputedAtom<T>(lifetime, debugName, pull, keepAlive);
86+
var atom = new ComputedAtom<T>(debugName, pull, keepAlive);
87+
lifetime.Register(atom);
88+
return atom;
8589
}
8690

8791
/// <summary>
@@ -117,7 +121,9 @@ public static Atom<T> Computed<T>(Lifetime lifetime, Func<T> pull,
117121
public static MutableAtom<T> Computed<T>(Lifetime lifetime, Func<T> pull, Action<T> push,
118122
bool keepAlive = false, string debugName = null)
119123
{
120-
return new MutableComputedAtom<T>(lifetime, debugName, pull, push, keepAlive);
124+
var atom = new MutableComputedAtom<T>(debugName, pull, push, keepAlive);
125+
lifetime.Register(atom);
126+
return atom;
121127
}
122128
}
123129
}

Runtime/Core/AtomBase.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public abstract class AtomBase : IEquatable<AtomBase>, IDisposable
1313

1414
[CanBeNull] internal static AtomBase Stack;
1515

16-
internal readonly string debugName;
16+
internal string debugName;
1717

1818
internal int childrenCount;
1919
internal AtomBase[] children;
@@ -24,14 +24,6 @@ public abstract class AtomBase : IEquatable<AtomBase>, IDisposable
2424
internal AtomOptions options;
2525
internal AtomState state = AtomState.Obsolete;
2626

27-
internal AtomBase(Lifetime lifetime, string debugName, AtomOptions options)
28-
{
29-
this.debugName = debugName;
30-
this.options = options;
31-
32-
lifetime.Register(this);
33-
}
34-
3527
public bool Equals(AtomBase other)
3628
{
3729
return ReferenceEquals(this, other);

Runtime/Core/CodeGenAtom.cs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Runtime.CompilerServices;
34
using Unity.IL2CPP.CompilerServices;
45

@@ -12,7 +13,66 @@ public static class CodeGenAtom
1213
public static ComputedAtom<T> Create<T>(ILifetimeScope scope, string debugName, Func<T> pull,
1314
bool keepAlive)
1415
{
15-
return new ComputedAtom<T>(scope.Lifetime, debugName, pull, keepAlive);
16+
var atom = new ComputedAtom<T>(debugName, pull, keepAlive);
17+
scope.Lifetime.Register(atom);
18+
return atom;
19+
}
20+
21+
public static ComputedAtom<T> CreatePooled<T>(ILifetimeScope scope, string debugName, Func<T> pull,
22+
bool keepAlive)
23+
{
24+
var atomPool = Pool<T>.Atoms;
25+
26+
ComputedAtom<T> atom;
27+
if (atomPool.Count > 0)
28+
{
29+
atom = atomPool.Pop();
30+
atom.Setup(debugName, pull, keepAlive);
31+
}
32+
else
33+
{
34+
atom = new ComputedAtom<T>(debugName, pull, keepAlive);
35+
}
36+
37+
var disposerPool = Pool<T>.Recyclers;
38+
var disposer = disposerPool.Count > 0 ? disposerPool.Pop() : new Recycler<T>();
39+
disposer.Setup(atom);
40+
41+
scope.Lifetime.Register(disposer);
42+
43+
return atom;
44+
}
45+
46+
internal static class Pool<T>
47+
{
48+
public static readonly Stack<ComputedAtom<T>> Atoms = new Stack<ComputedAtom<T>>();
49+
public static readonly Stack<Recycler<T>> Recyclers = new Stack<Recycler<T>>();
50+
51+
public static void Clear()
52+
{
53+
Atoms.Clear();
54+
Recyclers.Clear();
55+
}
56+
}
57+
58+
internal class Recycler<T> : IDisposable
59+
{
60+
private ComputedAtom<T> _atom;
61+
62+
public void Setup(ComputedAtom<T> atom)
63+
{
64+
_atom = atom;
65+
}
66+
67+
public void Dispose()
68+
{
69+
((IDisposable) _atom).Dispose();
70+
71+
Pool<T>.Atoms.Push(_atom);
72+
Pool<T>.Recyclers.Push(this);
73+
74+
_atom = null;
75+
}
1676
}
1777
}
1878
}

Runtime/Core/ComputedAtom.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,35 @@ namespace UniMob.Core
1111
[Il2CppSetOption(Option.ArrayBoundsChecks, false)]
1212
public class ComputedAtom<T> : AtomBase, Atom<T>
1313
{
14-
internal readonly Func<T> pull;
15-
internal readonly IEqualityComparer<T> comparer;
14+
internal Func<T> pull;
15+
internal IEqualityComparer<T> comparer;
1616

1717
internal T cache;
1818
internal ExceptionDispatchInfo exception;
1919

2020
internal ComputedAtom(
21-
Lifetime lifetime,
2221
string debugName,
2322
[NotNull] Func<T> pull,
2423
bool keepAlive = false)
25-
: base(lifetime, debugName, keepAlive ? AtomOptions.AutoActualize : AtomOptions.None)
2624
{
25+
this.debugName = debugName;
2726
this.pull = pull ?? throw new ArgumentNullException(nameof(pull));
27+
options = keepAlive ? AtomOptions.AutoActualize : AtomOptions.None;
2828
comparer = EqualityComparer<T>.Default;
2929
}
3030

31+
internal void Setup(string debugName, Func<T> pull, bool keepAlive = false)
32+
{
33+
if (!options.Has(AtomOptions.Disposed))
34+
{
35+
throw new InvalidOperationException("Cannot reuse non disposed atom");
36+
}
37+
38+
this.debugName = debugName;
39+
this.pull = pull ?? throw new ArgumentNullException(nameof(pull));
40+
options = keepAlive ? AtomOptions.AutoActualize : AtomOptions.None;
41+
}
42+
3143
// for CodeGen
3244
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3345
public bool DirectEvaluate()

Runtime/Core/MutableComputedAtom.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ public class MutableComputedAtom<T> : ComputedAtom<T>, MutableAtom<T>
1111
internal readonly Action<T> push;
1212

1313
internal MutableComputedAtom(
14-
Lifetime lifetime,
1514
string debugName,
1615
[NotNull] Func<T> pull,
1716
[NotNull] Action<T> push,
1817
bool keepAlive = false)
19-
: base(lifetime, debugName, pull, keepAlive)
18+
: base(debugName, pull, keepAlive)
2019
{
2120
this.push = push ?? throw new ArgumentNullException(nameof(push));
2221
}

Runtime/Core/ReactionAtom.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ public class ReactionAtom : AtomBase, Reaction
1212
internal readonly Action<Exception> exceptionHandler;
1313

1414
public ReactionAtom(
15-
Lifetime lifetime,
1615
string debugName,
1716
Action reaction,
1817
Action<Exception> exceptionHandler = null)
19-
: base(lifetime, debugName, AtomOptions.AutoActualize)
2018
{
19+
this.debugName = debugName;
2120
this.reaction = reaction ?? throw new ArgumentNullException(nameof(reaction));
2221
this.exceptionHandler = exceptionHandler ?? Debug.LogException;
22+
options = AtomOptions.AutoActualize;
2323
}
2424

2525
public void Activate(bool force = false)

Runtime/Core/ValueAtom.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ public class ValueAtom<T> : AtomBase, MutableAtom<T>
1111

1212
internal T value;
1313

14-
internal ValueAtom(Lifetime lifetime, string debugName, T value)
15-
: base(lifetime, debugName, AtomOptions.None)
14+
internal ValueAtom(string debugName, T value)
1615
{
16+
this.debugName = debugName;
1717
this.value = value;
18+
options = AtomOptions.None;
1819
comparer = EqualityComparer<T>.Default;
1920
}
2021

Runtime/Lifetime.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,15 @@ public interface ILifetimeScope
280280
{
281281
Lifetime Lifetime { get; }
282282
}
283+
284+
public static class LifetimeScopeExtension
285+
{
286+
public static void ThrowIfDisposed(ILifetimeScope scope)
287+
{
288+
if (scope.Lifetime.IsDisposed)
289+
{
290+
throw new ObjectDisposedException("Lifetime is disposed");
291+
}
292+
}
293+
}
283294
}

0 commit comments

Comments
 (0)