├── .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