├── .gitignore ├── .idea └── .idea.AlienSignals │ └── .idea │ ├── .gitignore │ ├── encodings.xml │ ├── indexLayout.xml │ ├── misc.xml │ └── vcs.xml ├── AlienSignals.csproj ├── AlienSignals.csproj.meta ├── AlienSignals.sln ├── AlienSignals.sln.meta ├── Core.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── Core.meta ├── Core │ ├── Computed.cs │ ├── Computed.cs.meta │ ├── Effect.cs │ ├── Effect.cs.meta │ ├── EffectScope.cs │ ├── EffectScope.cs.meta │ ├── Interfaces.meta │ ├── Interfaces │ │ ├── IDependency.cs │ │ ├── IDependency.cs.meta │ │ ├── ISubscriber.cs │ │ └── ISubscriber.cs.meta │ ├── Link.cs │ ├── Link.cs.meta │ ├── OneWayLink.cs │ ├── OneWayLink.cs.meta │ ├── Reactive.cs │ ├── Reactive.cs.meta │ ├── Signal.cs │ ├── Signal.cs.meta │ ├── SubscriberFlags.cs │ ├── SubscriberFlags.cs.meta │ ├── Systems.meta │ └── Systems │ │ ├── ReactivitySystem.cs │ │ └── ReactivitySystem.cs.meta └── dev.ctrl-neo.csharp-alien-signals.asmdef ├── bin.meta ├── global.json ├── global.json.meta ├── obj.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ -------------------------------------------------------------------------------- /.idea/.idea.AlienSignals/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /modules.xml 6 | /contentModel.xml 7 | /projectSettingsUpdater.xml 8 | /.idea.AlienSignals.iml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /.idea/.idea.AlienSignals/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/.idea.AlienSignals/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/.idea.AlienSignals/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Angular 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/.idea.AlienSignals/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AlienSignals.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | latestmajor 6 | disable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AlienSignals.csproj.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e5ba35be4694d4243baff23c2fb6e74a 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /AlienSignals.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlienSignals", "AlienSignals.csproj", "{2C79CB64-A41B-4127-8A85-294CD796BFF8}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {2C79CB64-A41B-4127-8A85-294CD796BFF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {2C79CB64-A41B-4127-8A85-294CD796BFF8}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {2C79CB64-A41B-4127-8A85-294CD796BFF8}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {2C79CB64-A41B-4127-8A85-294CD796BFF8}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /AlienSignals.sln.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 153201543381843dabd097ce92babda6 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Core.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6a4b5fcb9c48c44a68e666a8cb52bcc8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSharp-Alien-Signals 2 | 3 | A custom port of [Alien Signals](https://github.com/stackblitz/alien-signals) to C#. 4 | 5 | ## Usage 6 | 7 | ```csharp 8 | // Create a signal 9 | var count = Reactive.CreateSignal(0); 10 | 11 | // Create a computed that depends on the signal 12 | var doubled = Reactive.CreateComputed(prev => count.GetValue() * 2); 13 | 14 | // Create an effect that logs changes 15 | var disposeEffect = Reactive.CreateEffect(() => { 16 | Console.WriteLine($"Count: {count.GetValue()}, Doubled: {doubled.GetValue()}"); 17 | }); 18 | 19 | // Update the signal 20 | count.SetValue(5); // Will trigger the effect 21 | 22 | // Batch updates 23 | Reactive.StartBatch(); 24 | count.SetValue(10); 25 | count.SetValue(20); // Only one effect run at the end 26 | Reactive.EndBatch(); 27 | 28 | // Clean up 29 | disposeEffect.Stop(); 30 | ``` 31 | 32 | ## Installation 33 | 34 | ### NuGet 35 | 36 | Pending 37 | 38 | ### Unity 39 | 40 | 1. Open the Unity Package Manager. 41 | 2. Click the "+" button and select "Add package from git URL...". 42 | 3. Enter the URL of this repository. 43 | 4. Click "Add". 44 | 5. Wait for Unity to download and import the package. 45 | 6. Use the package in your scripts. 46 | 47 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d4c05dc0359b44b8dadfa49a776d102d 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1ac8d2c43b96f4a5f8d5f2e4a88653c8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Core.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: de1caaf9fa3fd4abda51219723046247 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Core/Computed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AlienSignals.Runtime.Core.Interfaces; 3 | 4 | namespace AlienSignals.Runtime.Core 5 | { 6 | public class Computed : ISubscriber, IDependency 7 | { 8 | public Func Getter { get; } 9 | public T CurrentValue { get; set; } 10 | public SubscriberFlags Flags { get; set; } 11 | public Link Subs { get; set; } 12 | public Link SubsTail { get; set; } 13 | public Link Deps { get; set; } 14 | public Link DepsTail { get; set; } 15 | 16 | public Computed(Func getter) 17 | { 18 | Getter = getter; 19 | Flags = SubscriberFlags.Computed | SubscriberFlags.Dirty; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Runtime/Core/Computed.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 43e21b44eafca4f07940dc699b6cfca9 -------------------------------------------------------------------------------- /Runtime/Core/Effect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AlienSignals.Runtime.Core.Interfaces; 3 | 4 | namespace AlienSignals.Runtime.Core 5 | { 6 | public class Effect : ISubscriber, IDependency 7 | { 8 | public Action Fn { get; } 9 | public SubscriberFlags Flags { get; set; } 10 | public Link Subs { get; set; } 11 | public Link SubsTail { get; set; } 12 | public Link Deps { get; set; } 13 | public Link DepsTail { get; set; } 14 | 15 | public Effect(Action fn) 16 | { 17 | Fn = fn; 18 | Flags = SubscriberFlags.Effect; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Runtime/Core/Effect.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a0453f7f51fd14956b6132ea62e50d0d -------------------------------------------------------------------------------- /Runtime/Core/EffectScope.cs: -------------------------------------------------------------------------------- 1 | using AlienSignals.Runtime.Core.Interfaces; 2 | 3 | namespace AlienSignals.Runtime.Core 4 | { 5 | public class EffectScope : ISubscriber 6 | { 7 | public bool IsScope { get; } = true; 8 | public SubscriberFlags Flags { get; set; } 9 | public Link Deps { get; set; } 10 | public Link DepsTail { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Runtime/Core/EffectScope.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 19afbccbb3d1c4fcea50cc2b30c5f615 -------------------------------------------------------------------------------- /Runtime/Core/Interfaces.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cf3d5aa76eaa6415ebab0ce55292d973 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Core/Interfaces/IDependency.cs: -------------------------------------------------------------------------------- 1 | namespace AlienSignals.Runtime.Core.Interfaces 2 | { 3 | public interface IDependency 4 | { 5 | Link Subs { get; set; } 6 | Link SubsTail { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /Runtime/Core/Interfaces/IDependency.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d2030dab9b6634e05bf3567662644471 -------------------------------------------------------------------------------- /Runtime/Core/Interfaces/ISubscriber.cs: -------------------------------------------------------------------------------- 1 | namespace AlienSignals.Runtime.Core.Interfaces 2 | { 3 | public interface ISubscriber 4 | { 5 | SubscriberFlags Flags { get; set; } 6 | Link Deps { get; set; } 7 | Link DepsTail { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /Runtime/Core/Interfaces/ISubscriber.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8669014cb07d34e468e1300d6b01280c -------------------------------------------------------------------------------- /Runtime/Core/Link.cs: -------------------------------------------------------------------------------- 1 | using AlienSignals.Runtime.Core.Interfaces; 2 | 3 | namespace AlienSignals.Runtime.Core 4 | { 5 | public class Link 6 | { 7 | public IDependency Dep { get; set; } 8 | public ISubscriber Sub { get; set; } 9 | public Link PrevSub { get; set; } 10 | public Link NextSub { get; set; } 11 | public Link NextDep { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Runtime/Core/Link.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 38af91f3fc13a4df6b7e18fffcc516a5 -------------------------------------------------------------------------------- /Runtime/Core/OneWayLink.cs: -------------------------------------------------------------------------------- 1 | namespace AlienSignals.Runtime.Core 2 | { 3 | public class OneWayLink 4 | { 5 | public T Target { get; set; } 6 | public OneWayLink Linked { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /Runtime/Core/OneWayLink.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 26a7ed4762a204782bd9edb53fc77143 -------------------------------------------------------------------------------- /Runtime/Core/Reactive.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using AlienSignals.Runtime.Core.Interfaces; 4 | using AlienSignals.Runtime.Core.Systems; 5 | 6 | namespace AlienSignals.Runtime.Core 7 | { 8 | public static class Reactive 9 | { 10 | private static readonly Stack _pauseStack = new Stack(); 11 | private static readonly ReactiveSystem _system; 12 | private static int _batchDepth = 0; 13 | private static ISubscriber _activeSub = null; 14 | private static EffectScope _activeScope = null; 15 | 16 | static Reactive() 17 | { 18 | _system = new ReactiveSystem(UpdateComputed, NotifyEffect); 19 | } 20 | 21 | public static void StartBatch() 22 | { 23 | _batchDepth++; 24 | } 25 | 26 | public static void EndBatch() 27 | { 28 | if (--_batchDepth == 0) 29 | { 30 | _system.ProcessEffectNotifications(); 31 | } 32 | } 33 | 34 | public static void PauseTracking() 35 | { 36 | _pauseStack.Push(_activeSub); 37 | _activeSub = null; 38 | } 39 | 40 | public static void ResumeTracking() 41 | { 42 | _activeSub = _pauseStack.Pop(); 43 | } 44 | 45 | public static Signal CreateSignal(T initialValue = default) 46 | { 47 | return new Signal(initialValue); 48 | } 49 | 50 | public static Computed CreateComputed(Func getter) 51 | { 52 | return new Computed(getter); 53 | } 54 | 55 | public static Effect CreateEffect(Action fn) 56 | { 57 | var e = new Effect(fn); 58 | if (_activeSub != null) 59 | { 60 | _system.Link(e, _activeSub); 61 | } 62 | else if (_activeScope != null) 63 | { 64 | _system.Link(e, _activeScope); 65 | } 66 | 67 | var prevSub = _activeSub; 68 | _activeSub = e; 69 | try 70 | { 71 | e.Fn(); 72 | } 73 | finally 74 | { 75 | _activeSub = prevSub; 76 | } 77 | return e; 78 | } 79 | 80 | public static EffectScope CreateEffectScope(Action fn) 81 | { 82 | var e = new EffectScope(); 83 | var prevScope = _activeScope; 84 | _activeScope = e; 85 | try 86 | { 87 | fn(); 88 | } 89 | finally 90 | { 91 | _activeScope = prevScope; 92 | } 93 | return e; 94 | } 95 | 96 | public static T GetValue(this Computed computed) 97 | { 98 | var flags = computed.Flags; 99 | if ((flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed)) != 0) 100 | { 101 | _system.ProcessComputedUpdate(computed, flags); 102 | } 103 | if (_activeSub != null) 104 | { 105 | _system.Link(computed, _activeSub); 106 | } 107 | else if (_activeScope != null) 108 | { 109 | _system.Link(computed, _activeScope); 110 | } 111 | return computed.CurrentValue; 112 | } 113 | 114 | public static T GetValue(this Signal signal) 115 | { 116 | if (_activeSub != null) 117 | { 118 | _system.Link(signal, _activeSub); 119 | } 120 | return signal.CurrentValue; 121 | } 122 | 123 | public static void SetValue(this Signal signal, T value) 124 | { 125 | if (!EqualityComparer.Default.Equals(signal.CurrentValue, value)) 126 | { 127 | signal.CurrentValue = value; 128 | var subs = signal.Subs; 129 | if (subs != null) 130 | { 131 | _system.Propagate(subs); 132 | if (_batchDepth == 0) 133 | { 134 | _system.ProcessEffectNotifications(); 135 | } 136 | } 137 | } 138 | } 139 | 140 | public static void Stop(this ISubscriber subscriber) 141 | { 142 | _system.StartTracking(subscriber); 143 | _system.EndTracking(subscriber); 144 | } 145 | 146 | private static bool UpdateComputed(ISubscriber computed) 147 | { 148 | var prevSub = _activeSub; 149 | _activeSub = computed; 150 | _system.StartTracking(computed); 151 | try 152 | { 153 | var computedT = computed as Computed; 154 | var oldValue = computedT.CurrentValue; 155 | var newValue = computedT.Getter(oldValue); 156 | if (!EqualityComparer.Default.Equals(oldValue, newValue)) 157 | { 158 | computedT.CurrentValue = newValue; 159 | return true; 160 | } 161 | return false; 162 | } 163 | finally 164 | { 165 | _activeSub = prevSub; 166 | _system.EndTracking(computed); 167 | } 168 | } 169 | 170 | private static bool NotifyEffect(ISubscriber e) 171 | { 172 | if (e is EffectScope scope) 173 | { 174 | return NotifyEffectScope(scope); 175 | } 176 | else if (e is Effect effect) 177 | { 178 | return NotifyEffect(effect); 179 | } 180 | return false; 181 | } 182 | 183 | private static bool NotifyEffect(Effect e) 184 | { 185 | var flags = e.Flags; 186 | if ((flags & SubscriberFlags.Dirty) != 0 187 | || ((flags & SubscriberFlags.PendingComputed) != 0 && _system.UpdateDirtyFlag(e, flags))) 188 | { 189 | var prevSub = _activeSub; 190 | _activeSub = e; 191 | _system.StartTracking(e); 192 | try 193 | { 194 | e.Fn(); 195 | } 196 | finally 197 | { 198 | _activeSub = prevSub; 199 | _system.EndTracking(e); 200 | } 201 | } 202 | else 203 | { 204 | _system.ProcessPendingInnerEffects(e, e.Flags); 205 | } 206 | return true; 207 | } 208 | 209 | private static bool NotifyEffectScope(EffectScope e) 210 | { 211 | var flags = e.Flags; 212 | if ((flags & SubscriberFlags.PendingEffect) != 0) 213 | { 214 | _system.ProcessPendingInnerEffects(e, e.Flags); 215 | return true; 216 | } 217 | return false; 218 | } 219 | } 220 | } -------------------------------------------------------------------------------- /Runtime/Core/Reactive.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 16eae889eec7445648704733ad653243 -------------------------------------------------------------------------------- /Runtime/Core/Signal.cs: -------------------------------------------------------------------------------- 1 | using AlienSignals.Runtime.Core.Interfaces; 2 | 3 | namespace AlienSignals.Runtime.Core 4 | { 5 | public class Signal : IDependency 6 | { 7 | public T CurrentValue { get; set; } 8 | public Link Subs { get; set; } 9 | public Link SubsTail { get; set; } 10 | 11 | public Signal(T initialValue) 12 | { 13 | CurrentValue = initialValue; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Runtime/Core/Signal.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bbb267bf52d90482988eafd96a517448 -------------------------------------------------------------------------------- /Runtime/Core/SubscriberFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AlienSignals.Runtime.Core 4 | { 5 | [Flags] 6 | public enum SubscriberFlags 7 | { 8 | None = 0, 9 | Computed = 1 << 0, 10 | Effect = 1 << 1, 11 | Tracking = 1 << 2, 12 | Notified = 1 << 3, 13 | Recursed = 1 << 4, 14 | Dirty = 1 << 5, 15 | PendingComputed = 1 << 6, 16 | PendingEffect = 1 << 7, 17 | Propagated = Dirty | PendingComputed | PendingEffect 18 | } 19 | } -------------------------------------------------------------------------------- /Runtime/Core/SubscriberFlags.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 27648ba8919f5490984705de356bc7c2 -------------------------------------------------------------------------------- /Runtime/Core/Systems.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cb996708a967446ebafcbd0270908c6e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Core/Systems/ReactivitySystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using AlienSignals.Runtime.Core.Interfaces; 4 | 5 | namespace AlienSignals.Runtime.Core.Systems 6 | { 7 | public class ReactiveSystem 8 | { 9 | private readonly Func _updateComputed; 10 | private readonly Func _notifyEffect; 11 | 12 | private readonly List _notifyBuffer = new List(); 13 | private int _notifyIndex = 0; 14 | private int _notifyBufferLength = 0; 15 | 16 | public ReactiveSystem(Func updateComputed, Func notifyEffect) 17 | { 18 | _updateComputed = updateComputed; 19 | _notifyEffect = notifyEffect; 20 | } 21 | 22 | public Link Link(IDependency dep, ISubscriber sub) 23 | { 24 | var currentDep = sub.DepsTail; 25 | if (currentDep != null && currentDep.Dep == dep) 26 | { 27 | return null; 28 | } 29 | 30 | var nextDep = currentDep != null ? currentDep.NextDep : sub.Deps; 31 | if (nextDep != null && nextDep.Dep == dep) 32 | { 33 | sub.DepsTail = nextDep; 34 | return null; 35 | } 36 | 37 | var depLastSub = dep.SubsTail; 38 | if (depLastSub != null && depLastSub.Sub == sub && IsValidLink(depLastSub, sub)) 39 | { 40 | return null; 41 | } 42 | 43 | return LinkNewDep(dep, sub, nextDep, currentDep); 44 | } 45 | 46 | public void Propagate(Link current) 47 | { 48 | var next = current.NextSub; 49 | OneWayLink branchs = null; 50 | var branchDepth = 0; 51 | var targetFlag = SubscriberFlags.Dirty; 52 | 53 | while (true) 54 | { 55 | var sub = current.Sub; 56 | var subFlags = sub.Flags; 57 | 58 | var shouldNotify = false; 59 | 60 | if ((subFlags & (SubscriberFlags.Tracking | SubscriberFlags.Recursed | SubscriberFlags.Propagated)) == 0) 61 | { 62 | sub.Flags = subFlags | targetFlag | SubscriberFlags.Notified; 63 | shouldNotify = true; 64 | } 65 | else if ((subFlags & SubscriberFlags.Recursed) != 0 && (subFlags & SubscriberFlags.Tracking) == 0) 66 | { 67 | sub.Flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag | SubscriberFlags.Notified; 68 | shouldNotify = true; 69 | } 70 | else if ((subFlags & SubscriberFlags.Propagated) == 0 && IsValidLink(current, sub)) 71 | { 72 | sub.Flags = subFlags | SubscriberFlags.Recursed | targetFlag | SubscriberFlags.Notified; 73 | shouldNotify = (sub as IDependency)?.Subs != null; 74 | } 75 | 76 | if (shouldNotify) 77 | { 78 | var subSubs = (sub as IDependency)?.Subs; 79 | if (subSubs != null) 80 | { 81 | current = subSubs; 82 | if (subSubs.NextSub != null) 83 | { 84 | branchs = new OneWayLink { Target = next, Linked = branchs }; 85 | branchDepth++; 86 | next = current.NextSub; 87 | targetFlag = SubscriberFlags.PendingComputed; 88 | continue; 89 | } 90 | else 91 | { 92 | targetFlag = (subFlags & SubscriberFlags.Effect) != 0 93 | ? SubscriberFlags.PendingEffect 94 | : SubscriberFlags.PendingComputed; 95 | } 96 | continue; 97 | } 98 | if ((subFlags & SubscriberFlags.Effect) != 0) 99 | { 100 | if (_notifyBuffer.Count <= _notifyBufferLength) 101 | _notifyBuffer.Add(sub); 102 | else 103 | _notifyBuffer[_notifyBufferLength] = sub; 104 | _notifyBufferLength++; 105 | } 106 | } 107 | else if ((subFlags & (SubscriberFlags.Tracking | targetFlag)) == 0) 108 | { 109 | sub.Flags = subFlags | targetFlag | SubscriberFlags.Notified; 110 | if ((subFlags & (SubscriberFlags.Effect | SubscriberFlags.Notified)) == SubscriberFlags.Effect) 111 | { 112 | if (_notifyBuffer.Count <= _notifyBufferLength) 113 | _notifyBuffer.Add(sub); 114 | else 115 | _notifyBuffer[_notifyBufferLength] = sub; 116 | _notifyBufferLength++; 117 | } 118 | } 119 | else if ((subFlags & targetFlag) == 0 120 | && (subFlags & SubscriberFlags.Propagated) != 0 121 | && IsValidLink(current, sub)) 122 | { 123 | sub.Flags = subFlags | targetFlag; 124 | } 125 | 126 | if ((current = next) != null) 127 | { 128 | next = current.NextSub; 129 | targetFlag = branchDepth > 0 130 | ? SubscriberFlags.PendingComputed 131 | : SubscriberFlags.Dirty; 132 | continue; 133 | } 134 | 135 | while (branchDepth > 0) 136 | { 137 | branchDepth--; 138 | current = branchs.Target; 139 | branchs = branchs.Linked; 140 | if (current != null) 141 | { 142 | next = current.NextSub; 143 | targetFlag = branchDepth > 0 144 | ? SubscriberFlags.PendingComputed 145 | : SubscriberFlags.Dirty; 146 | continue; 147 | } 148 | } 149 | 150 | break; 151 | } 152 | } 153 | 154 | public void StartTracking(ISubscriber sub) 155 | { 156 | sub.DepsTail = null; 157 | sub.Flags = (sub.Flags & ~(SubscriberFlags.Notified | SubscriberFlags.Recursed | SubscriberFlags.Propagated)) | SubscriberFlags.Tracking; 158 | } 159 | 160 | public void EndTracking(ISubscriber sub) 161 | { 162 | var depsTail = sub.DepsTail; 163 | if (depsTail != null) 164 | { 165 | var nextDep = depsTail.NextDep; 166 | if (nextDep != null) 167 | { 168 | ClearTracking(nextDep); 169 | depsTail.NextDep = null; 170 | } 171 | } 172 | else if (sub.Deps != null) 173 | { 174 | ClearTracking(sub.Deps); 175 | sub.Deps = null; 176 | } 177 | sub.Flags &= ~SubscriberFlags.Tracking; 178 | } 179 | 180 | public bool UpdateDirtyFlag(ISubscriber sub, SubscriberFlags flags) 181 | { 182 | if (CheckDirty(sub.Deps)) 183 | { 184 | sub.Flags = flags | SubscriberFlags.Dirty; 185 | return true; 186 | } 187 | else 188 | { 189 | sub.Flags = flags & ~SubscriberFlags.PendingComputed; 190 | return false; 191 | } 192 | } 193 | 194 | public void ProcessComputedUpdate(ISubscriber computed, SubscriberFlags flags) 195 | { 196 | if ((flags & SubscriberFlags.Dirty) != 0 || CheckDirty(computed.Deps)) 197 | { 198 | if (_updateComputed(computed)) 199 | { 200 | var subs = (computed as IDependency)?.Subs; 201 | if (subs != null) 202 | { 203 | ShallowPropagate(subs); 204 | } 205 | } 206 | } 207 | else 208 | { 209 | computed.Flags = flags & ~SubscriberFlags.PendingComputed; 210 | } 211 | } 212 | 213 | public void ProcessPendingInnerEffects(ISubscriber sub, SubscriberFlags flags) 214 | { 215 | if ((flags & SubscriberFlags.PendingEffect) != 0) 216 | { 217 | sub.Flags = flags & ~SubscriberFlags.PendingEffect; 218 | var link = sub.Deps; 219 | do 220 | { 221 | var dep = link.Dep; 222 | if (dep is ISubscriber depSub 223 | && (depSub.Flags & SubscriberFlags.Effect) != 0 224 | && (depSub.Flags & SubscriberFlags.Propagated) != 0) 225 | { 226 | _notifyEffect(depSub); 227 | } 228 | link = link.NextDep; 229 | } while (link != null); 230 | } 231 | } 232 | 233 | public void ProcessEffectNotifications() 234 | { 235 | while (_notifyIndex < _notifyBufferLength) 236 | { 237 | var effect = _notifyBuffer[_notifyIndex]; 238 | _notifyBuffer[_notifyIndex] = null; 239 | if (!_notifyEffect(effect)) 240 | { 241 | effect.Flags &= ~SubscriberFlags.Notified; 242 | } 243 | _notifyIndex++; 244 | } 245 | _notifyIndex = 0; 246 | _notifyBufferLength = 0; 247 | } 248 | 249 | private Link LinkNewDep(IDependency dep, ISubscriber sub, Link nextDep, Link depsTail) 250 | { 251 | var newLink = new Link 252 | { 253 | Dep = dep, 254 | Sub = sub, 255 | NextDep = nextDep, 256 | PrevSub = null, 257 | NextSub = null 258 | }; 259 | 260 | if (depsTail == null) 261 | { 262 | sub.Deps = newLink; 263 | } 264 | else 265 | { 266 | depsTail.NextDep = newLink; 267 | } 268 | 269 | if (dep.Subs == null) 270 | { 271 | dep.Subs = newLink; 272 | } 273 | else 274 | { 275 | var oldTail = dep.SubsTail; 276 | newLink.PrevSub = oldTail; 277 | oldTail.NextSub = newLink; 278 | } 279 | 280 | sub.DepsTail = newLink; 281 | dep.SubsTail = newLink; 282 | return newLink; 283 | } 284 | 285 | private bool CheckDirty(Link current) 286 | { 287 | OneWayLink prevLinks = null; 288 | var checkDepth = 0; 289 | var dirty = false; 290 | 291 | while (true) 292 | { 293 | dirty = false; 294 | var dep = current.Dep; 295 | 296 | if ((current.Sub.Flags & SubscriberFlags.Dirty) != 0) 297 | { 298 | dirty = true; 299 | } 300 | else if (dep is ISubscriber depSub) 301 | { 302 | var depFlags = depSub.Flags; 303 | if ((depFlags & (SubscriberFlags.Computed | SubscriberFlags.Dirty)) == (SubscriberFlags.Computed | SubscriberFlags.Dirty)) 304 | { 305 | if (_updateComputed(depSub)) 306 | { 307 | var subs = (depSub as IDependency)?.Subs; 308 | if (subs?.NextSub != null) 309 | { 310 | ShallowPropagate(subs); 311 | } 312 | dirty = true; 313 | } 314 | } 315 | else if ((depFlags & (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) == (SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) 316 | { 317 | if (current.NextSub != null || current.PrevSub != null) 318 | { 319 | prevLinks = new OneWayLink { Target = current, Linked = prevLinks }; 320 | } 321 | current = depSub.Deps; 322 | checkDepth++; 323 | continue; 324 | } 325 | } 326 | 327 | if (!dirty && current.NextDep != null) 328 | { 329 | current = current.NextDep; 330 | continue; 331 | } 332 | 333 | while (checkDepth > 0) 334 | { 335 | checkDepth--; 336 | var sub = current.Sub as ISubscriber; 337 | var firstSub = (sub as IDependency)?.Subs; 338 | if (dirty) 339 | { 340 | if (_updateComputed(sub)) 341 | { 342 | if (firstSub?.NextSub != null) 343 | { 344 | current = prevLinks.Target; 345 | prevLinks = prevLinks.Linked; 346 | ShallowPropagate(firstSub); 347 | } 348 | else 349 | { 350 | current = firstSub; 351 | } 352 | continue; 353 | } 354 | } 355 | else 356 | { 357 | sub.Flags &= ~SubscriberFlags.PendingComputed; 358 | } 359 | if (firstSub?.NextSub != null) 360 | { 361 | current = prevLinks.Target; 362 | prevLinks = prevLinks.Linked; 363 | } 364 | else 365 | { 366 | current = firstSub; 367 | } 368 | if (current?.NextDep != null) 369 | { 370 | current = current.NextDep; 371 | continue; 372 | } 373 | dirty = false; 374 | } 375 | 376 | return dirty; 377 | } 378 | } 379 | 380 | private void ShallowPropagate(Link link) 381 | { 382 | do 383 | { 384 | var sub = link.Sub; 385 | var subFlags = sub.Flags; 386 | if ((subFlags & (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty)) == SubscriberFlags.PendingComputed) 387 | { 388 | sub.Flags = subFlags | SubscriberFlags.Dirty | SubscriberFlags.Notified; 389 | if ((subFlags & (SubscriberFlags.Effect | SubscriberFlags.Notified)) == SubscriberFlags.Effect) 390 | { 391 | if (_notifyBuffer.Count <= _notifyBufferLength) 392 | _notifyBuffer.Add(sub); 393 | else 394 | _notifyBuffer[_notifyBufferLength] = sub; 395 | _notifyBufferLength++; 396 | } 397 | } 398 | link = link.NextSub; 399 | } while (link != null); 400 | } 401 | 402 | private bool IsValidLink(Link checkLink, ISubscriber sub) 403 | { 404 | var depsTail = sub.DepsTail; 405 | if (depsTail != null) 406 | { 407 | var link = sub.Deps; 408 | do 409 | { 410 | if (link == checkLink) 411 | { 412 | return true; 413 | } 414 | if (link == depsTail) 415 | { 416 | break; 417 | } 418 | link = link.NextDep; 419 | } while (link != null); 420 | } 421 | return false; 422 | } 423 | 424 | private void ClearTracking(Link link) 425 | { 426 | do 427 | { 428 | var dep = link.Dep; 429 | var nextDep = link.NextDep; 430 | var nextSub = link.NextSub; 431 | var prevSub = link.PrevSub; 432 | 433 | if (nextSub != null) 434 | { 435 | nextSub.PrevSub = prevSub; 436 | } 437 | else 438 | { 439 | dep.SubsTail = prevSub; 440 | } 441 | 442 | if (prevSub != null) 443 | { 444 | prevSub.NextSub = nextSub; 445 | } 446 | else 447 | { 448 | dep.Subs = nextSub; 449 | } 450 | 451 | if (dep.Subs == null && dep is ISubscriber depSub) 452 | { 453 | var depFlags = depSub.Flags; 454 | if ((depFlags & SubscriberFlags.Dirty) == 0) 455 | { 456 | depSub.Flags = depFlags | SubscriberFlags.Dirty; 457 | } 458 | var depDeps = depSub.Deps; 459 | if (depDeps != null) 460 | { 461 | link = depDeps; 462 | depSub.DepsTail.NextDep = nextDep; 463 | depSub.Deps = null; 464 | depSub.DepsTail = null; 465 | continue; 466 | } 467 | } 468 | link = nextDep; 469 | } while (link != null); 470 | } 471 | } 472 | } -------------------------------------------------------------------------------- /Runtime/Core/Systems/ReactivitySystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a18f67b9edbb243d98ed20939bde58a8 -------------------------------------------------------------------------------- /Runtime/dev.ctrl-neo.csharp-alien-signals.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dev.ctrl-neo.csharp-alien-signals", 3 | "references": [], 4 | "includePlatforms": [], 5 | "excludePlatforms": [], 6 | "allowUnsafeCode": false, 7 | "overrideReferences": false, 8 | "precompiledReferences": [], 9 | "autoReferenced": true, 10 | "defineConstraints": [], 11 | "versionDefines": [], 12 | "noEngineReferences": false 13 | } 14 | -------------------------------------------------------------------------------- /bin.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 20bece537ee5c48e3bc2ea45b85432e4 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.0", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /global.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7dc2caa1203f7432e9584356d25d8c5f 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /obj.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0f438bb7e5bcc4b61a2969a0f92e5110 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dev.ctrl-neo.csharp-alien-signals", 3 | "version": "1.0.0", 4 | "description": "A custom port of stackblitz/alien-signals to C#.", 5 | "author": "CTRl Neo Studios", 6 | "license": "MIT", 7 | "displayName": "CSharp Alien Signals", 8 | "unity": "2021.3" 9 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 20c34df7447b44d1f9b8c499bf962366 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------