├── .github
└── workflows
│ ├── codeql-analysis.yml
│ ├── dotnet.yml
│ └── nuget.yml
├── .gitignore
├── Directory.Build.props
├── LICENSE
├── ManagedCode.Orleans.StateMachine.Tests
├── Cluster
│ ├── Grains
│ │ ├── Interfaces
│ │ │ ├── Constants.cs
│ │ │ ├── ITestGrain.cs
│ │ │ ├── ITestOrleansContextGrain.cs
│ │ │ └── ITestStatelessGrain.cs
│ │ ├── TestGrain.cs
│ │ ├── TestOrleansContextGrain.cs
│ │ └── TestStatelessGrain.cs
│ ├── TestClientConfigurations.cs
│ ├── TestClusterApplication.cs
│ └── TestSiloConfigurations.cs
├── ManagedCode.Orleans.StateMachine.Tests.csproj
└── StateMachineGrainTests.cs
├── ManagedCode.Orleans.StateMachine
├── Extensions
│ └── StateMachineExtensions.cs
├── Interfaces
│ └── IStateMachineGrain.cs
├── ManagedCode.Orleans.StateMachine.csproj
├── Models
│ ├── OrleansStateInfo.cs
│ └── OrleansStateMachineInfo.cs
├── Properties
│ └── launchSettings.json
└── StateMachineGrain.cs
├── Orleans.StateMachine.sln
├── README.md
└── logo.png
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main ]
20 | schedule:
21 | - cron: '35 11 * * 4'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 |
28 | strategy:
29 | fail-fast: false
30 | matrix:
31 | language: [ 'csharp' ]
32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
33 | # Learn more:
34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
35 |
36 | steps:
37 | - name: Checkout repository
38 | uses: actions/checkout@v2
39 |
40 | # Initializes the CodeQL tools for scanning.
41 | - name: Initialize CodeQL
42 | uses: github/codeql-action/init@v1
43 | with:
44 | languages: ${{ matrix.language }}
45 | # If you wish to specify custom queries, you can do so here or in a config file.
46 | # By default, queries listed here will override any specified in a config file.
47 | # Prefix the list here with "+" to use these queries and those in the config file.
48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
49 |
50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
51 | # If this step fails, then you should remove it and run the build manually (see below)
52 | - name: Autobuild
53 | uses: github/codeql-action/autobuild@v1
54 |
55 | # ℹ️ Command-line programs to run using the OS shell.
56 | # 📚 https://git.io/JvXDl
57 |
58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
59 | # and modify them (or add more) to build your code if your project
60 | # uses a compiled language
61 |
62 | #- run: |
63 | # make bootstrap
64 | # make release
65 |
66 | - name: Perform CodeQL Analysis
67 | uses: github/codeql-action/analyze@v1
68 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | name: .NET
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 |
10 | # Allows you to run this workflow manually from the Actions tab
11 | workflow_dispatch:
12 |
13 | jobs:
14 |
15 | build-and-test:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Setup .NET
20 | uses: actions/setup-dotnet@v3
21 | with:
22 | dotnet-version: 9.0.x
23 |
24 | # run build and test
25 | - name: Restore dependencies
26 | run: dotnet restore
27 | - name: Build
28 | run: dotnet build --no-restore
29 | - name: Test
30 | run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=ManagedCode.Orleans.StateMachine.Tests/lcov.info
31 |
32 | #- name: coverlet
33 | # uses: b3b00/coverlet-action@1.1.9
34 | # with:
35 | # testProject: 'ManagedCode.Database.Tests/ManagedCode.Database.Tests.csproj'
36 | # output: 'lcov.info'
37 | # outputFormat: 'lcov'
38 | # excludes: '[program]*,[test]test.*'
39 | #- name: coveralls
40 | # uses: coverallsapp/github-action@master
41 | # with:
42 | # github-token: ${{secrets.GITHUB_TOKEN }}
43 | # path-to-lcov: ManagedCode.Storage.Tests/lcov.info
44 |
--------------------------------------------------------------------------------
/.github/workflows/nuget.yml:
--------------------------------------------------------------------------------
1 | name: nuget
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 |
7 | # Allows you to run this workflow manually from the Actions tab
8 | workflow_dispatch:
9 |
10 | jobs:
11 | nuget-pack:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v3
17 | - name: Setup .NET
18 | uses: actions/setup-dotnet@v3
19 | with:
20 | dotnet-version: 9.0.x
21 |
22 | - name: Restore dependencies
23 | run: dotnet restore
24 | - name: Build
25 | run: dotnet build --configuration Release
26 | - name: Test
27 | run: dotnet test --configuration Release
28 | - name: Pack
29 | run: dotnet pack -p:IncludeSymbols=false -p:SymbolPackageFormat=snupkg --configuration Release
30 |
31 | - name: publish nuget packages
32 | run: |
33 | shopt -s globstar
34 | for file in **/*.nupkg
35 | do
36 | dotnet nuget push "$file" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
37 | done
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider
4 |
5 | ### Intellij ###
6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
8 |
9 | # User-specific stuff
10 | .idea/**/workspace.xml
11 | .idea/**/tasks.xml
12 | .idea/**/usage.statistics.xml
13 | .idea/**/dictionaries
14 | .idea/**/shelf
15 |
16 | # Generated files
17 | .idea/**/contentModel.xml
18 |
19 | # Sensitive or high-churn files
20 | .idea/**/dataSources/
21 | .idea/**/dataSources.ids
22 | .idea/**/dataSources.local.xml
23 | .idea/**/sqlDataSources.xml
24 | .idea/**/dynamic.xml
25 | .idea/**/uiDesigner.xml
26 | .idea/**/dbnavigator.xml
27 |
28 | # Gradle
29 | .idea/**/gradle.xml
30 | .idea/**/libraries
31 |
32 | # Gradle and Maven with auto-import
33 | # When using Gradle or Maven with auto-import, you should exclude module files,
34 | # since they will be recreated, and may cause churn. Uncomment if using
35 | # auto-import.
36 | # .idea/artifacts
37 | # .idea/compiler.xml
38 | # .idea/jarRepositories.xml
39 | # .idea/modules.xml
40 | # .idea/*.iml
41 | # .idea/modules
42 | # *.iml
43 | # *.ipr
44 |
45 | # CMake
46 | cmake-build-*/
47 |
48 | # Mongo Explorer plugin
49 | .idea/**/mongoSettings.xml
50 |
51 | # File-based project format
52 | *.iws
53 |
54 | # IntelliJ
55 | out/
56 |
57 | # mpeltonen/sbt-idea plugin
58 | .idea_modules/
59 |
60 | # JIRA plugin
61 | atlassian-ide-plugin.xml
62 |
63 | # Cursive Clojure plugin
64 | .idea/replstate.xml
65 |
66 | # Crashlytics plugin (for Android Studio and IntelliJ)
67 | com_crashlytics_export_strings.xml
68 | crashlytics.properties
69 | crashlytics-build.properties
70 | fabric.properties
71 |
72 | # Editor-based Rest Client
73 | .idea/httpRequests
74 |
75 | # Android studio 3.1+ serialized cache file
76 | .idea/caches/build_file_checksums.ser
77 |
78 | ### Intellij Patch ###
79 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
80 |
81 | # *.iml
82 | # modules.xml
83 | # .idea/misc.xml
84 | # *.ipr
85 |
86 | # Sonarlint plugin
87 | # https://plugins.jetbrains.com/plugin/7973-sonarlint
88 | .idea/**/sonarlint/
89 |
90 | # SonarQube Plugin
91 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
92 | .idea/**/sonarIssues.xml
93 |
94 | # Markdown Navigator plugin
95 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
96 | .idea/**/markdown-navigator.xml
97 | .idea/**/markdown-navigator-enh.xml
98 | .idea/**/markdown-navigator/
99 |
100 | # Cache file creation bug
101 | # See https://youtrack.jetbrains.com/issue/JBR-2257
102 | .idea/$CACHE_FILE$
103 |
104 | # CodeStream plugin
105 | # https://plugins.jetbrains.com/plugin/12206-codestream
106 | .idea/codestream.xml
107 |
108 | ### Intellij+all ###
109 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
110 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
111 |
112 | # User-specific stuff
113 |
114 | # Generated files
115 |
116 | # Sensitive or high-churn files
117 |
118 | # Gradle
119 |
120 | # Gradle and Maven with auto-import
121 | # When using Gradle or Maven with auto-import, you should exclude module files,
122 | # since they will be recreated, and may cause churn. Uncomment if using
123 | # auto-import.
124 | # .idea/artifacts
125 | # .idea/compiler.xml
126 | # .idea/jarRepositories.xml
127 | # .idea/modules.xml
128 | # .idea/*.iml
129 | # .idea/modules
130 | # *.iml
131 | # *.ipr
132 |
133 | # CMake
134 |
135 | # Mongo Explorer plugin
136 |
137 | # File-based project format
138 |
139 | # IntelliJ
140 |
141 | # mpeltonen/sbt-idea plugin
142 |
143 | # JIRA plugin
144 |
145 | # Cursive Clojure plugin
146 |
147 | # Crashlytics plugin (for Android Studio and IntelliJ)
148 |
149 | # Editor-based Rest Client
150 |
151 | # Android studio 3.1+ serialized cache file
152 |
153 | ### Intellij+all Patch ###
154 | # Ignores the whole .idea folder and all .iml files
155 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
156 |
157 | .idea/
158 |
159 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
160 |
161 | *.iml
162 | modules.xml
163 | .idea/misc.xml
164 | *.ipr
165 |
166 | # Sonarlint plugin
167 | .idea/sonarlint
168 |
169 | ### Linux ###
170 | *~
171 |
172 | # temporary files which can be created if a process still has a handle open of a deleted file
173 | .fuse_hidden*
174 |
175 | # KDE directory preferences
176 | .directory
177 |
178 | # Linux trash folder which might appear on any partition or disk
179 | .Trash-*
180 |
181 | # .nfs files are created when an open file is removed but is still being accessed
182 | .nfs*
183 |
184 | ### macOS ###
185 | # General
186 | .DS_Store
187 | .AppleDouble
188 | .LSOverride
189 |
190 | # Icon must end with two \r
191 | Icon
192 |
193 |
194 | # Thumbnails
195 | ._*
196 |
197 | # Files that might appear in the root of a volume
198 | .DocumentRevisions-V100
199 | .fseventsd
200 | .Spotlight-V100
201 | .TemporaryItems
202 | .Trashes
203 | .VolumeIcon.icns
204 | .com.apple.timemachine.donotpresent
205 |
206 | # Directories potentially created on remote AFP share
207 | .AppleDB
208 | .AppleDesktop
209 | Network Trash Folder
210 | Temporary Items
211 | .apdisk
212 |
213 | ### Rider ###
214 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
215 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
216 |
217 | # User-specific stuff
218 |
219 | # Generated files
220 |
221 | # Sensitive or high-churn files
222 |
223 | # Gradle
224 |
225 | # Gradle and Maven with auto-import
226 | # When using Gradle or Maven with auto-import, you should exclude module files,
227 | # since they will be recreated, and may cause churn. Uncomment if using
228 | # auto-import.
229 | # .idea/artifacts
230 | # .idea/compiler.xml
231 | # .idea/jarRepositories.xml
232 | # .idea/modules.xml
233 | # .idea/*.iml
234 | # .idea/modules
235 | # *.iml
236 | # *.ipr
237 |
238 | # CMake
239 |
240 | # Mongo Explorer plugin
241 |
242 | # File-based project format
243 |
244 | # IntelliJ
245 |
246 | # mpeltonen/sbt-idea plugin
247 |
248 | # JIRA plugin
249 |
250 | # Cursive Clojure plugin
251 |
252 | # Crashlytics plugin (for Android Studio and IntelliJ)
253 |
254 | # Editor-based Rest Client
255 |
256 | # Android studio 3.1+ serialized cache file
257 |
258 | ### VisualStudioCode ###
259 | .vscode/*
260 | !.vscode/tasks.json
261 | !.vscode/launch.json
262 | *.code-workspace
263 |
264 | ### VisualStudioCode Patch ###
265 | # Ignore all local history of files
266 | .history
267 | .ionide
268 |
269 | ### Windows ###
270 | # Windows thumbnail cache files
271 | Thumbs.db
272 | Thumbs.db:encryptable
273 | ehthumbs.db
274 | ehthumbs_vista.db
275 |
276 | # Dump file
277 | *.stackdump
278 |
279 | # Folder config file
280 | [Dd]esktop.ini
281 |
282 | # Recycle Bin used on file shares
283 | $RECYCLE.BIN/
284 |
285 | # Windows Installer files
286 | *.cab
287 | *.msi
288 | *.msix
289 | *.msm
290 | *.msp
291 |
292 | # Windows shortcuts
293 | *.lnk
294 |
295 | ### VisualStudio ###
296 | ## Ignore Visual Studio temporary files, build results, and
297 | ## files generated by popular Visual Studio add-ons.
298 | ##
299 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
300 |
301 | # User-specific files
302 | *.rsuser
303 | *.suo
304 | *.user
305 | *.userosscache
306 | *.sln.docstates
307 |
308 | # User-specific files (MonoDevelop/Xamarin Studio)
309 | *.userprefs
310 |
311 | # Mono auto generated files
312 | mono_crash.*
313 |
314 | # Build results
315 | [Dd]ebug/
316 | [Dd]ebugPublic/
317 | [Rr]elease/
318 | [Rr]eleases/
319 | x64/
320 | x86/
321 | [Aa][Rr][Mm]/
322 | [Aa][Rr][Mm]64/
323 | bld/
324 | [Bb]in/
325 | [Oo]bj/
326 | [Ll]og/
327 | [Ll]ogs/
328 |
329 | # Visual Studio 2015/2017 cache/options directory
330 | .vs/
331 | # Uncomment if you have tasks that create the project's static files in wwwroot
332 | #wwwroot/
333 |
334 | # Visual Studio 2017 auto generated files
335 | Generated\ Files/
336 |
337 | # MSTest test Results
338 | [Tt]est[Rr]esult*/
339 | [Bb]uild[Ll]og.*
340 |
341 | # NUnit
342 | *.VisualState.xml
343 | TestResult.xml
344 | nunit-*.xml
345 |
346 | # Build Results of an ATL Project
347 | [Dd]ebugPS/
348 | [Rr]eleasePS/
349 | dlldata.c
350 |
351 | # Benchmark Results
352 | BenchmarkDotNet.Artifacts/
353 |
354 | # .NET Core
355 | project.lock.json
356 | project.fragment.lock.json
357 | artifacts/
358 |
359 | # StyleCop
360 | StyleCopReport.xml
361 |
362 | # Files built by Visual Studio
363 | *_i.c
364 | *_p.c
365 | *_h.h
366 | *.ilk
367 | *.meta
368 | *.obj
369 | *.iobj
370 | *.pch
371 | *.pdb
372 | *.ipdb
373 | *.pgc
374 | *.pgd
375 | *.rsp
376 | *.sbr
377 | *.tlb
378 | *.tli
379 | *.tlh
380 | *.tmp
381 | *.tmp_proj
382 | *_wpftmp.csproj
383 | *.log
384 | *.vspscc
385 | *.vssscc
386 | .builds
387 | *.pidb
388 | *.svclog
389 | *.scc
390 |
391 | # Chutzpah Test files
392 | _Chutzpah*
393 |
394 | # Visual C++ cache files
395 | ipch/
396 | *.aps
397 | *.ncb
398 | *.opendb
399 | *.opensdf
400 | *.sdf
401 | *.cachefile
402 | *.VC.db
403 | *.VC.VC.opendb
404 |
405 | # Visual Studio profiler
406 | *.psess
407 | *.vsp
408 | *.vspx
409 | *.sap
410 |
411 | # Visual Studio Trace Files
412 | *.e2e
413 |
414 | # TFS 2012 Local Workspace
415 | $tf/
416 |
417 | # Guidance Automation Toolkit
418 | *.gpState
419 |
420 | # ReSharper is a .NET coding add-in
421 | _ReSharper*/
422 | *.[Rr]e[Ss]harper
423 | *.DotSettings.user
424 |
425 | # TeamCity is a build add-in
426 | _TeamCity*
427 |
428 | # DotCover is a Code Coverage Tool
429 | *.dotCover
430 |
431 | # AxoCover is a Code Coverage Tool
432 | .axoCover/*
433 | !.axoCover/settings.json
434 |
435 | # Coverlet is a free, cross platform Code Coverage Tool
436 | coverage*[.json, .xml, .info]
437 |
438 | # Visual Studio code coverage results
439 | *.coverage
440 | *.coveragexml
441 |
442 | # NCrunch
443 | _NCrunch_*
444 | .*crunch*.local.xml
445 | nCrunchTemp_*
446 |
447 | # MightyMoose
448 | *.mm.*
449 | AutoTest.Net/
450 |
451 | # Web workbench (sass)
452 | .sass-cache/
453 |
454 | # Installshield output folder
455 | [Ee]xpress/
456 |
457 | # DocProject is a documentation generator add-in
458 | DocProject/buildhelp/
459 | DocProject/Help/*.HxT
460 | DocProject/Help/*.HxC
461 | DocProject/Help/*.hhc
462 | DocProject/Help/*.hhk
463 | DocProject/Help/*.hhp
464 | DocProject/Help/Html2
465 | DocProject/Help/html
466 |
467 | # Click-Once directory
468 | publish/
469 |
470 | # Publish Web Output
471 | *.[Pp]ublish.xml
472 | *.azurePubxml
473 | # Note: Comment the next line if you want to checkin your web deploy settings,
474 | # but database connection strings (with potential passwords) will be unencrypted
475 | *.pubxml
476 | *.publishproj
477 |
478 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
479 | # checkin your Azure Web App publish settings, but sensitive information contained
480 | # in these scripts will be unencrypted
481 | PublishScripts/
482 |
483 | # NuGet Packages
484 | *.nupkg
485 | # NuGet Symbol Packages
486 | *.snupkg
487 | # The packages folder can be ignored because of Package Restore
488 | **/[Pp]ackages/*
489 | # except build/, which is used as an MSBuild target.
490 | !**/[Pp]ackages/build/
491 | # Uncomment if necessary however generally it will be regenerated when needed
492 | #!**/[Pp]ackages/repositories.config
493 | # NuGet v3's project.json files produces more ignorable files
494 | *.nuget.props
495 | *.nuget.targets
496 |
497 | # Microsoft Azure Build Output
498 | csx/
499 | *.build.csdef
500 |
501 | # Microsoft Azure Emulator
502 | ecf/
503 | rcf/
504 |
505 | # Windows Store app package directories and files
506 | AppPackages/
507 | BundleArtifacts/
508 | Package.StoreAssociation.xml
509 | _pkginfo.txt
510 | *.appx
511 | *.appxbundle
512 | *.appxupload
513 |
514 | # Visual Studio cache files
515 | # files ending in .cache can be ignored
516 | *.[Cc]ache
517 | # but keep track of directories ending in .cache
518 | !?*.[Cc]ache/
519 |
520 | # Others
521 | ClientBin/
522 | ~$*
523 | *.dbmdl
524 | *.dbproj.schemaview
525 | *.jfm
526 | *.pfx
527 | *.publishsettings
528 | orleans.codegen.cs
529 |
530 | # Including strong name files can present a security risk
531 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
532 | #*.snk
533 |
534 | # Since there are multiple workflows, uncomment next line to ignore bower_components
535 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
536 | #bower_components/
537 |
538 | # RIA/Silverlight projects
539 | Generated_Code/
540 |
541 | # Backup & report files from converting an old project file
542 | # to a newer Visual Studio version. Backup files are not needed,
543 | # because we have git ;-)
544 | _UpgradeReport_Files/
545 | Backup*/
546 | UpgradeLog*.XML
547 | UpgradeLog*.htm
548 | ServiceFabricBackup/
549 | *.rptproj.bak
550 |
551 | # SQL Server files
552 | *.mdf
553 | *.ldf
554 | *.ndf
555 |
556 | # Business Intelligence projects
557 | *.rdl.data
558 | *.bim.layout
559 | *.bim_*.settings
560 | *.rptproj.rsuser
561 | *- [Bb]ackup.rdl
562 | *- [Bb]ackup ([0-9]).rdl
563 | *- [Bb]ackup ([0-9][0-9]).rdl
564 |
565 | # Microsoft Fakes
566 | FakesAssemblies/
567 |
568 | # GhostDoc plugin setting file
569 | *.GhostDoc.xml
570 |
571 | # Node.js Tools for Visual Studio
572 | .ntvs_analysis.dat
573 | node_modules/
574 |
575 | # Visual Studio 6 build log
576 | *.plg
577 |
578 | # Visual Studio 6 workspace options file
579 | *.opt
580 |
581 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
582 | *.vbw
583 |
584 | # Visual Studio LightSwitch build output
585 | **/*.HTMLClient/GeneratedArtifacts
586 | **/*.DesktopClient/GeneratedArtifacts
587 | **/*.DesktopClient/ModelManifest.xml
588 | **/*.Server/GeneratedArtifacts
589 | **/*.Server/ModelManifest.xml
590 | _Pvt_Extensions
591 |
592 | # Paket dependency manager
593 | .paket/paket.exe
594 | paket-files/
595 |
596 | # FAKE - F# Make
597 | .fake/
598 |
599 | # CodeRush personal settings
600 | .cr/personal
601 |
602 | # Python Tools for Visual Studio (PTVS)
603 | __pycache__/
604 | *.pyc
605 |
606 | # Cake - Uncomment if you are using it
607 | # tools/**
608 | # !tools/packages.config
609 |
610 | # Tabs Studio
611 | *.tss
612 |
613 | # Telerik's JustMock configuration file
614 | *.jmconfig
615 |
616 | # BizTalk build output
617 | *.btp.cs
618 | *.btm.cs
619 | *.odx.cs
620 | *.xsd.cs
621 |
622 | # OpenCover UI analysis results
623 | OpenCover/
624 |
625 | # Azure Stream Analytics local run output
626 | ASALocalRun/
627 |
628 | # MSBuild Binary and Structured Log
629 | *.binlog
630 |
631 | # NVidia Nsight GPU debugger configuration file
632 | *.nvuser
633 |
634 | # MFractors (Xamarin productivity tool) working folder
635 | .mfractor/
636 |
637 | # Local History for Visual Studio
638 | .localhistory/
639 |
640 | # BeatPulse healthcheck temp database
641 | healthchecksdb
642 |
643 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
644 | MigrationBackup/
645 |
646 | # Ionide (cross platform F# VS Code tools) working folder
647 | .ionide/
648 |
649 | # End of https://www.toptal.com/developers/gitignore/api/intellij,intellij+all,macos,linux,windows,visualstudio,visualstudiocode,rider
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ManagedCode
5 | Copyright © 2021-$([System.DateTime]::Now.ToString(`yyyy`)) ManagedCode SAS
6 | true
7 | true
8 | true
9 | snupkg
10 | Github
11 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
12 | logo.png
13 | MIT
14 | true
15 | README.md
16 |
17 | https://github.com/managedcode/Orleans.StateMachine
18 | https://github.com/managedcode/Orleans.StateMachine
19 | Managed Code - Orleans StateMachine
20 | 0.1.1
21 | 0.1.1
22 |
23 |
24 |
25 | true
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | all
34 | runtime; build; native; contentfiles; analyzers; buildtransitive
35 |
36 |
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Managed-Code
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/Cluster/Grains/Interfaces/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains.Interfaces;
2 |
3 | public static class Constants
4 | {
5 | public const string On = "On";
6 | public const string Off = "Off";
7 | public const char Space = ' ';
8 | }
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/Cluster/Grains/Interfaces/ITestGrain.cs:
--------------------------------------------------------------------------------
1 | namespace ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains.Interfaces;
2 |
3 | public interface ITestGrain : IGrainWithStringKey
4 | {
5 | Task Do(char input);
6 | }
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/Cluster/Grains/Interfaces/ITestOrleansContextGrain.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using ManagedCode.Orleans.StateMachine.Interfaces;
4 | using Orleans;
5 |
6 | namespace ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains.Interfaces;
7 |
8 | // Use enums instead of string constants
9 | public enum TestOrleansContextStates
10 | {
11 | Initial,
12 | Active,
13 | Processing,
14 | Final
15 | }
16 |
17 | public enum TestOrleansContextTriggers
18 | {
19 | Activate,
20 | Process,
21 | Complete,
22 | Reset,
23 | Deactivate
24 | }
25 |
26 | public interface ITestOrleansContextGrain : IGrainWithStringKey, IStateMachineGrain
27 | {
28 | Task> GetExecutionLog();
29 | Task ClearLog();
30 | }
31 |
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/Cluster/Grains/Interfaces/ITestStatelessGrain.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Orleans.StateMachine.Interfaces;
2 |
3 | namespace ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains.Interfaces;
4 |
5 | public interface ITestStatelessGrain : IGrainWithStringKey, IStateMachineGrain
6 | {
7 | Task DoSomethingElse(char input);
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/Cluster/Grains/TestGrain.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains.Interfaces;
2 | using Stateless;
3 |
4 | namespace ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains;
5 |
6 | public class TestGrain : StateMachineGrain, ITestGrain
7 | {
8 | public Task Do(char input)
9 | {
10 | StateMachine.Fire(input);
11 | return Task.FromResult(StateMachine.State);
12 | }
13 |
14 | protected override StateMachine BuildStateMachine()
15 | {
16 | // Instantiate a new state machine in the 'off' state
17 | var onOffSwitch = new StateMachine(Constants.Off);
18 |
19 | // Configure state machine with the Configure method, supplying the state to be configured as a parameter
20 | onOffSwitch.Configure(Constants.Off).Permit(Constants.Space, Constants.On);
21 | onOffSwitch.Configure(Constants.On).Permit(Constants.Space, Constants.Off);
22 |
23 | return onOffSwitch;
24 | }
25 | }
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/Cluster/Grains/TestOrleansContextGrain.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using ManagedCode.Orleans.StateMachine.Extensions;
4 | using ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains.Interfaces;
5 | using Stateless;
6 |
7 | namespace ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains;
8 |
9 | public class TestOrleansContextGrain : StateMachineGrain, ITestOrleansContextGrain
10 | {
11 | private readonly List _executionLog = new();
12 |
13 | public Task> GetExecutionLog()
14 | {
15 | return Task.FromResult(_executionLog);
16 | }
17 |
18 | public Task ClearLog()
19 | {
20 | _executionLog.Clear();
21 | return Task.CompletedTask;
22 | }
23 |
24 | private Task Log(string message)
25 | {
26 | _executionLog.Add(message);
27 | // Simulate async work
28 | return Task.Delay(1);
29 | }
30 |
31 | protected override StateMachine BuildStateMachine()
32 | {
33 | var machine = new StateMachine(TestOrleansContextStates.Initial);
34 |
35 | machine.Configure(TestOrleansContextStates.Initial)
36 | .OnActivateOrleansContextAsync(() => Log("Initial.Activate"))
37 | .OnExitOrleansContextAsync(() => Log("Initial.Exit"))
38 | .Permit(TestOrleansContextTriggers.Activate, TestOrleansContextStates.Active);
39 |
40 | machine.Configure(TestOrleansContextStates.Active)
41 | .OnEntryOrleansContextAsync(() => Log("Active.Entry"))
42 | .OnEntryFromOrleansContextAsync(TestOrleansContextTriggers.Activate, () => Log("Active.EntryFrom.Activate"))
43 | .OnEntryFromOrleansContextAsync(TestOrleansContextTriggers.Reset, (StateMachine.Transition t) => Log($"Active.EntryFrom.Reset (via {t.Trigger})"))
44 | .OnExitOrleansContextAsync(() => Log("Active.Exit"))
45 | .OnActivateOrleansContextAsync(() => Log("Active.Activate"))
46 | .OnDeactivateOrleansContextAsync(() => Log("Active.Deactivate"))
47 | .Permit(TestOrleansContextTriggers.Process, TestOrleansContextStates.Processing)
48 | .Permit(TestOrleansContextTriggers.Deactivate, TestOrleansContextStates.Final);
49 |
50 | machine.Configure(TestOrleansContextStates.Processing)
51 | .OnEntryOrleansContextAsync((StateMachine.Transition t) => Log($"Processing.Entry (via {t.Trigger})"))
52 | .OnEntryFromOrleansContextAsync(TestOrleansContextTriggers.Process, (StateMachine.Transition t) => Log($"Processing.EntryFrom.Process (id:{t.Parameters?[0] ?? "?"})"))
53 | .OnExitOrleansContextAsync((StateMachine.Transition t) => Log($"Processing.Exit (to {t.Destination})"))
54 | .Permit(TestOrleansContextTriggers.Complete, TestOrleansContextStates.Final)
55 | .Permit(TestOrleansContextTriggers.Reset, TestOrleansContextStates.Active);
56 |
57 | machine.Configure(TestOrleansContextStates.Final)
58 | .OnEntryOrleansContextAsync(() => Log("Final.Entry"))
59 | .OnEntryFromOrleansContextAsync(TestOrleansContextTriggers.Complete, (StateMachine.Transition t) =>
60 | {
61 | var msg = t.Parameters?[0] ?? "?";
62 | var success = t.Parameters?.Length > 1 && t.Parameters[1] is bool b
63 | ? b.ToString().ToLowerInvariant() // Ensure boolean is lowercase for logging
64 | : "?";
65 | return Log($"Final.EntryFrom.Complete (msg:{msg}, success:{success})");
66 | })
67 | .Permit(TestOrleansContextTriggers.Reset, TestOrleansContextStates.Active)
68 | .OnExitOrleansContextAsync(() => Log("Final.Exit"))
69 | .OnActivateOrleansContextAsync(() => Log("Final.Activate"))
70 | .OnDeactivateOrleansContextAsync(() => Log("Final.Deactivate"));
71 |
72 | return machine;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/Cluster/Grains/TestStatelessGrain.cs:
--------------------------------------------------------------------------------
1 | using ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains.Interfaces;
2 | using Stateless;
3 |
4 | namespace ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains;
5 |
6 | public class TestStatelessGrain : StateMachineGrain, ITestStatelessGrain
7 | {
8 | public Task DoSomethingElse(char input)
9 | {
10 | return Task.FromResult("OK");
11 | }
12 |
13 | protected override StateMachine BuildStateMachine()
14 | {
15 | // Instantiate a new state machine in the 'off' state
16 | var onOffSwitch = new StateMachine(Constants.Off);
17 |
18 | // Configure state machine with the Configure method, supplying the state to be configured as a parameter
19 | onOffSwitch.Configure(Constants.Off).Permit(Constants.Space, Constants.On);
20 | onOffSwitch.Configure(Constants.On).Permit(Constants.Space, Constants.Off);
21 |
22 | return onOffSwitch;
23 | }
24 | }
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/Cluster/TestClientConfigurations.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using Orleans.TestingHost;
3 |
4 | namespace ManagedCode.Orleans.StateMachine.Tests.Cluster;
5 |
6 | public class TestClientConfigurations : IClientBuilderConfigurator
7 | {
8 | public void Configure(IConfiguration configuration, IClientBuilder clientBuilder)
9 | {
10 | //clientBuilder.();
11 | }
12 | }
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/Cluster/TestClusterApplication.cs:
--------------------------------------------------------------------------------
1 | using Orleans.TestingHost;
2 | using Xunit;
3 |
4 | namespace ManagedCode.Orleans.StateMachine.Tests.Cluster;
5 |
6 | [CollectionDefinition(nameof(TestClusterApplication))]
7 | public class TestClusterApplication : ICollectionFixture
8 | {
9 | public TestClusterApplication()
10 | {
11 | var builder = new TestClusterBuilder();
12 | builder.AddSiloBuilderConfigurator();
13 | builder.AddClientBuilderConfigurator();
14 | Cluster = builder.Build();
15 | Cluster.Deploy();
16 | }
17 |
18 | public TestCluster Cluster { get; }
19 | }
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/Cluster/TestSiloConfigurations.cs:
--------------------------------------------------------------------------------
1 | using Orleans.TestingHost;
2 |
3 | namespace ManagedCode.Orleans.StateMachine.Tests.Cluster;
4 |
5 | public class TestSiloConfigurations : ISiloConfigurator
6 | {
7 | public void Configure(ISiloBuilder siloBuilder)
8 | {
9 | //siloBuilder.AddOrleansRateLimiting();
10 | }
11 | }
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/ManagedCode.Orleans.StateMachine.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 |
8 | false
9 |
10 | 13
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | all
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 |
21 |
22 |
23 |
24 |
25 |
26 | runtime; build; native; contentfiles; analyzers; buildtransitive
27 | all
28 |
29 |
30 | all
31 | runtime; build; native; contentfiles; analyzers; buildtransitive
32 |
33 |
34 | runtime; build; native; contentfiles; analyzers; buildtransitive
35 | all
36 |
37 |
38 | all
39 | runtime; build; native; contentfiles; analyzers; buildtransitive
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine.Tests/StateMachineGrainTests.cs:
--------------------------------------------------------------------------------
1 | using FluentAssertions;
2 | using ManagedCode.Orleans.StateMachine.Tests.Cluster;
3 | using ManagedCode.Orleans.StateMachine.Tests.Cluster.Grains.Interfaces;
4 | using Xunit;
5 | using Xunit.Abstractions;
6 | using System.Collections.Generic; // Added for List
7 | using Stateless; // Added for StateMachine
8 |
9 | namespace ManagedCode.Orleans.StateMachine.Tests;
10 |
11 | [Collection(nameof(TestClusterApplication))]
12 | public class StateMachineGrainTests
13 | {
14 | private readonly ITestOutputHelper _outputHelper;
15 | private readonly TestClusterApplication _testApp;
16 |
17 | public StateMachineGrainTests(TestClusterApplication testApp, ITestOutputHelper outputHelper)
18 | {
19 | _testApp = testApp;
20 | _outputHelper = outputHelper;
21 | }
22 |
23 | [Fact]
24 | public async Task TestGrainTests()
25 | {
26 | var grain = _testApp.Cluster.Client.GetGrain("test");
27 |
28 | var state = await grain.Do(' ');
29 | state.Should().Be(Constants.On);
30 |
31 | state = await grain.Do(' ');
32 | state.Should().Be(Constants.Off);
33 |
34 | //No valid leaving transitions are permitted from state 'Off' for trigger 'x'. Consider ignoring the trigger.
35 | await Assert.ThrowsAsync(
36 | () => grain.Do('x'));
37 | }
38 |
39 | [Fact]
40 | public async Task TestStatelessGrainTests()
41 | {
42 | var grain = _testApp.Cluster.Client.GetGrain("test-stateless"); // Changed key
43 |
44 | await grain.FireAsync(' ');
45 | (await grain.GetStateAsync()).Should().Be(Constants.On);
46 |
47 | await grain.FireAsync(' ');
48 | (await grain.GetStateAsync()).Should().Be(Constants.Off);
49 |
50 | //No valid leaving transitions are permitted from state 'Off' for trigger 'x'. Consider ignoring the trigger.
51 | await Assert.ThrowsAsync(
52 | () => grain.FireAsync('x'));
53 |
54 | var into = await grain.GetInfoAsync();
55 | into.InitialState.UnderlyingState.Should().Be(Constants.Off);
56 | }
57 |
58 | [Fact]
59 | public async Task OrleansContextExtensions_ExecuteInOrder()
60 | {
61 | var grain = _testApp.Cluster.Client.GetGrain("test-orleans-context");
62 | await grain.ClearLog();
63 |
64 | // 1. Initial Activation
65 | await grain.ActivateAsync();
66 | var log = await grain.GetExecutionLog();
67 | log.Should().ContainInOrder("Initial.Activate");
68 | (await grain.GetStateAsync()).Should().Be(TestOrleansContextStates.Initial);
69 | await grain.ClearLog();
70 |
71 | // 2. Transition Initial -> Active
72 | await grain.FireAsync(TestOrleansContextTriggers.Activate);
73 | log = await grain.GetExecutionLog();
74 | log.Should().ContainInOrder(
75 | "Initial.Exit", // Expect OnExit during transition
76 | "Active.Entry",
77 | "Active.EntryFrom.Activate"
78 | );
79 | (await grain.GetStateAsync()).Should().Be(TestOrleansContextStates.Active);
80 | await grain.ClearLog();
81 |
82 | // 3. Activate Active state (should trigger OnActivate)
83 | await grain.ActivateAsync();
84 | log = await grain.GetExecutionLog();
85 | log.Should().ContainInOrder("Active.Activate");
86 | await grain.ClearLog();
87 |
88 | // 4. Transition Active -> Processing (with parameters)
89 | await grain.FireAsync(TestOrleansContextTriggers.Process, 123);
90 | log = await grain.GetExecutionLog();
91 | log.Should().ContainInOrder(
92 | "Active.Exit", // Expect OnExit during transition
93 | "Processing.Entry (via Process)",
94 | "Processing.EntryFrom.Process (id:123)"
95 | );
96 | (await grain.GetStateAsync()).Should().Be(TestOrleansContextStates.Processing);
97 | await grain.ClearLog();
98 |
99 | // 5. Transition Processing -> Final (with parameters)
100 | await grain.FireAsync(TestOrleansContextTriggers.Complete, "Done", true);
101 | log = await grain.GetExecutionLog();
102 | log.Should().ContainInOrder(
103 | "Processing.Exit (to Final)",
104 | "Final.Entry",
105 | "Final.EntryFrom.Complete (msg:Done, success:true)"
106 | );
107 | (await grain.GetStateAsync()).Should().Be(TestOrleansContextStates.Final);
108 | await grain.ClearLog();
109 |
110 | // 6. Activate Final state
111 | await grain.ActivateAsync();
112 | log = await grain.GetExecutionLog();
113 | log.Should().ContainInOrder("Final.Activate");
114 | await grain.ClearLog();
115 |
116 | // 7. Deactivate Final state (should not trigger OnExit as it's not a transition)
117 | await grain.DeactivateAsync();
118 | log = await grain.GetExecutionLog();
119 | log.Should().ContainInOrder("Final.Deactivate");
120 | await grain.ClearLog();
121 |
122 | // 8. Reset from Processing back to Active (test OnEntryFrom with Transition)
123 | (await grain.CanFireAsync(TestOrleansContextTriggers.Activate)).Should().BeFalse(); // Go back to Active first
124 | await grain.ClearLog();
125 |
126 | await grain.FireAsync(TestOrleansContextTriggers.Reset, "Reason", 99, false);
127 | log = await grain.GetExecutionLog();
128 | log.Should().ContainInOrder(
129 | "Final.Exit", // Exit Processing
130 | "Active.Entry", // Enter Active
131 | "Active.EntryFrom.Reset (via Reset)" // Specific EntryFrom for Reset
132 | );
133 | (await grain.GetStateAsync()).Should().Be(TestOrleansContextStates.Active);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine/Extensions/StateMachineExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Stateless;
4 |
5 | namespace ManagedCode.Orleans.StateMachine.Extensions;
6 |
7 | ///
8 | /// Provides extension methods for configuring Stateless state machines to run entry, exit, and trigger actions
9 | /// within the Orleans context, ensuring correct synchronization and execution semantics.
10 | ///
11 | public static class StateMachineExtensions
12 | {
13 | ///
14 | /// Configures the state machine to execute the specified entry action within the Orleans context asynchronously.
15 | ///
16 | /// The type representing states.
17 | /// The type representing events.
18 | /// The state configuration.
19 | /// The asynchronous entry action to execute.
20 | /// Optional description of the entry action.
21 | /// The updated state configuration.
22 | public static StateMachine.StateConfiguration OnEntryOrleansContextAsync(
23 | this StateMachine.StateConfiguration machine,
24 | Func entryAction, string? entryActionDescription = null)
25 | {
26 | return machine.OnEntryAsync(() => Task.Factory.StartNew(entryAction).Unwrap(), entryActionDescription);
27 | }
28 |
29 | ///
30 | /// Configures the state machine to execute the specified entry action with transition information within the Orleans context asynchronously.
31 | ///
32 | public static StateMachine.StateConfiguration OnEntryOrleansContextAsync(
33 | this StateMachine.StateConfiguration machine,
34 | Func.Transition, Task> entryAction, string? entryActionDescription = null)
35 | {
36 | return machine.OnEntryAsync(t => Task.Factory.StartNew(() => entryAction(t)).Unwrap(), entryActionDescription);
37 | }
38 |
39 | ///
40 | /// Configures the state machine to execute the specified exit action within the Orleans context asynchronously.
41 | ///
42 | public static StateMachine.StateConfiguration OnExitOrleansContextAsync(
43 | this StateMachine.StateConfiguration machine,
44 | Func exitAction, string? exitActionDescription = null)
45 | {
46 | return machine.OnExitAsync(() => Task.Factory.StartNew(exitAction).Unwrap(), exitActionDescription);
47 | }
48 |
49 | ///
50 | /// Configures the state machine to execute the specified exit action with transition information within the Orleans context asynchronously.
51 | ///
52 | public static StateMachine.StateConfiguration OnExitOrleansContextAsync(
53 | this StateMachine.StateConfiguration machine,
54 | Func.Transition, Task> exitAction, string? exitActionDescription = null)
55 | {
56 | return machine.OnExitAsync(t => Task.Factory.StartNew(() => exitAction(t)).Unwrap(), exitActionDescription);
57 | }
58 |
59 | ///
60 | /// Configures the state machine to execute the specified entry action when entering from a specific trigger within the Orleans context asynchronously.
61 | ///
62 | public static StateMachine.StateConfiguration OnEntryFromOrleansContextAsync(
63 | this StateMachine.StateConfiguration machine,
64 | TEvent trigger, Func entryAction, string? entryActionDescription = null)
65 | {
66 | return machine.OnEntryFromAsync(trigger, () => Task.Factory.StartNew(entryAction).Unwrap(), entryActionDescription);
67 | }
68 |
69 | ///
70 | /// Configures the state machine to execute the specified entry action with transition information when entering from a specific trigger within the Orleans context asynchronously.
71 | ///
72 | public static StateMachine.StateConfiguration OnEntryFromOrleansContextAsync(
73 | this StateMachine.StateConfiguration machine,
74 | TEvent trigger, Func.Transition, Task> entryAction, string? entryActionDescription = null)
75 | {
76 | return machine.OnEntryFromAsync(trigger, t => Task.Factory.StartNew(() => entryAction(t)).Unwrap(), entryActionDescription);
77 | }
78 |
79 | ///
80 | /// Configures the state machine to execute the specified entry action with one argument when entering from a parameterized trigger within the Orleans context asynchronously.
81 | ///
82 | public static StateMachine.StateConfiguration OnEntryFromOrleansContextAsync(
83 | this StateMachine.StateConfiguration machine,
84 | StateMachine.TriggerWithParameters trigger, Func entryAction, string? entryActionDescription = null)
85 | {
86 | return machine.OnEntryFromAsync(trigger, arg0 => Task.Factory.StartNew(() => entryAction(arg0)).Unwrap(), entryActionDescription);
87 | }
88 |
89 | ///
90 | /// Configures the state machine to execute the specified entry action with one argument and transition information when entering from a parameterized trigger within the Orleans context asynchronously.
91 | ///
92 | public static StateMachine.StateConfiguration OnEntryFromOrleansContextAsync(
93 | this StateMachine.StateConfiguration machine,
94 | StateMachine.TriggerWithParameters trigger, Func.Transition, Task> entryAction, string? entryActionDescription = null)
95 | {
96 | return machine.OnEntryFromAsync(trigger, (arg0, t) => Task.Factory.StartNew(() => entryAction(arg0, t)).Unwrap(), entryActionDescription);
97 | }
98 |
99 | ///
100 | /// Configures the state machine to execute the specified entry action with two arguments when entering from a parameterized trigger within the Orleans context asynchronously.
101 | ///
102 | public static StateMachine.StateConfiguration OnEntryFromOrleansContextAsync(
103 | this StateMachine.StateConfiguration machine,
104 | StateMachine.TriggerWithParameters trigger, Func entryAction, string? entryActionDescription = null)
105 | {
106 | return machine.OnEntryFromAsync(trigger, (arg0, arg1) => Task.Factory.StartNew(() => entryAction(arg0, arg1)).Unwrap(), entryActionDescription);
107 | }
108 |
109 | ///
110 | /// Configures the state machine to execute the specified entry action with two arguments and transition information when entering from a parameterized trigger within the Orleans context asynchronously.
111 | ///
112 | public static StateMachine.StateConfiguration OnEntryFromOrleansContextAsync(
113 | this StateMachine.StateConfiguration machine,
114 | StateMachine.TriggerWithParameters trigger, Func.Transition, Task> entryAction, string? entryActionDescription = null)
115 | {
116 | return machine.OnEntryFromAsync(trigger, (arg0, arg1, t) => Task.Factory.StartNew(() => entryAction(arg0, arg1, t)).Unwrap(), entryActionDescription);
117 | }
118 |
119 | ///
120 | /// Configures the state machine to execute the specified entry action with three arguments when entering from a parameterized trigger within the Orleans context asynchronously.
121 | ///
122 | public static StateMachine.StateConfiguration OnEntryFromOrleansContextAsync(
123 | this StateMachine.StateConfiguration machine,
124 | StateMachine.TriggerWithParameters trigger, Func entryAction, string? entryActionDescription = null)
125 | {
126 | return machine.OnEntryFromAsync(trigger, (arg0, arg1, arg2) => Task.Factory.StartNew(() => entryAction(arg0, arg1, arg2)).Unwrap(), entryActionDescription);
127 | }
128 |
129 | ///
130 | /// Configures the state machine to execute the specified entry action with three arguments and transition information when entering from a parameterized trigger within the Orleans context asynchronously.
131 | ///
132 | public static StateMachine.StateConfiguration OnEntryFromOrleansContextAsync(
133 | this StateMachine.StateConfiguration machine,
134 | StateMachine.TriggerWithParameters trigger, Func.Transition, Task> entryAction, string? entryActionDescription = null)
135 | {
136 | return machine.OnEntryFromAsync(trigger, (arg0, arg1, arg2, t) => Task.Factory.StartNew(() => entryAction(arg0, arg1, arg2, t)).Unwrap(), entryActionDescription);
137 | }
138 |
139 | ///
140 | /// Configures the state machine to execute the specified activation action within the Orleans context asynchronously.
141 | ///
142 | public static StateMachine.StateConfiguration OnActivateOrleansContextAsync(
143 | this StateMachine.StateConfiguration machine,
144 | Func activateAction, string? activateActionDescription = null)
145 | {
146 | return machine.OnActivateAsync(() => Task.Factory.StartNew(activateAction).Unwrap(), activateActionDescription);
147 | }
148 |
149 | ///
150 | /// Configures the state machine to execute the specified deactivation action within the Orleans context asynchronously.
151 | ///
152 | public static StateMachine.StateConfiguration OnDeactivateOrleansContextAsync(
153 | this StateMachine.StateConfiguration machine,
154 | Func deactivateAction, string? deactivateActionDescription = null)
155 | {
156 | return machine.OnDeactivateAsync(() => Task.Factory.StartNew(deactivateAction).Unwrap(), deactivateActionDescription);
157 | }
158 | }
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine/Interfaces/IStateMachineGrain.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using ManagedCode.Orleans.StateMachine.Models;
5 | using Stateless;
6 |
7 | namespace ManagedCode.Orleans.StateMachine.Interfaces;
8 |
9 | ///
10 | /// Represents a state machine grain that manages states and transitions.
11 | ///
12 | /// The type representing the states.
13 | /// The type representing the triggers that cause state transitions.
14 | public interface IStateMachineGrain
15 | {
16 | ///
17 | /// Activates current state in asynchronous fashion. Actions associated with activating the currrent state
18 | /// will be invoked. The activation is idempotent and subsequent activation of the same current state
19 | /// will not lead to re-execution of activation callbacks.
20 | ///
21 | /// A task that represents the asynchronous operation.
22 | Task ActivateAsync();
23 |
24 | ///
25 | /// Deactivates current state in asynchronous fashion. Actions associated with deactivating the currrent state
26 | /// will be invoked. The deactivation is idempotent and subsequent deactivation of the same current state
27 | /// will not lead to re-execution of deactivation callbacks.
28 | ///
29 | /// A task that represents the asynchronous operation.
30 | Task DeactivateAsync();
31 |
32 | ///
33 | /// Transition from the current state via the specified trigger in async fashion.
34 | /// The target state is determined by the configuration of the current state.
35 | /// Actions associated with leaving the current state and entering the new one
36 | /// will be invoked.
37 | ///
38 | /// The trigger to fire.
39 | /// A task that represents the asynchronous operation.
40 | ///
41 | /// The current state does
42 | /// not allow the trigger to be fired.
43 | ///
44 | Task FireAsync(TTrigger trigger);
45 |
46 | ///
47 | /// Asynchronously transitions from the current state via the specified trigger with one parameter.
48 | /// The target state is determined by the configuration of the current state.
49 | /// Actions associated with leaving the current state and entering the new one will be invoked.
50 | ///
51 | /// Type of the first trigger argument.
52 | /// The trigger to fire.
53 | /// The first argument.
54 | /// A task that represents the asynchronous operation.
55 | /// The current state does not allow the trigger to be fired.
56 | Task FireAsync(TTrigger trigger, TArg0 arg0);
57 |
58 | ///
59 | /// Asynchronously transitions from the current state via the specified trigger with two parameters.
60 | /// The target state is determined by the configuration of the current state.
61 | /// Actions associated with leaving the current state and entering the new one will be invoked.
62 | ///
63 | /// Type of the first trigger argument.
64 | /// Type of the second trigger argument.
65 | /// The trigger to fire.
66 | /// The first argument.
67 | /// The second argument.
68 | /// A task that represents the asynchronous operation.
69 | /// The current state does not allow the trigger to be fired.
70 | Task FireAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1);
71 |
72 | ///
73 | /// Asynchronously transitions from the current state via the specified trigger with three parameters.
74 | /// The target state is determined by the configuration of the current state.
75 | /// Actions associated with leaving the current state and entering the new one will be invoked.
76 | ///
77 | /// Type of the first trigger argument.
78 | /// Type of the second trigger argument.
79 | /// Type of the third trigger argument.
80 | /// The trigger to fire.
81 | /// The first argument.
82 | /// The second argument.
83 | /// The third argument.
84 | /// A task that represents the asynchronous operation.
85 | /// The current state does not allow the trigger to be fired.
86 | Task FireAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1, TArg2 arg2);
87 |
88 | ///
89 | /// Gets the current state of the state machine asynchronously.
90 | ///
91 | /// A task that represents the asynchronous operation. The task result contains the current state.
92 | Task GetStateAsync();
93 |
94 | ///
95 | /// Determines if the state machine is currently in the specified state or one of its substates asynchronously.
96 | ///
97 | /// The state to test for.
98 | ///
99 | /// A task that represents the asynchronous operation. The task result contains true if the current state is equal to, or a substate of,
100 | /// the supplied state; otherwise, false.
101 | ///
102 | Task IsInStateAsync(TState state);
103 |
104 | ///
105 | /// Checks if the specified trigger can be fired in the current state asynchronously.
106 | ///
107 | /// The trigger to test.
108 | ///
109 | /// A task that represents the asynchronous operation. The task result contains true if the trigger can be fired in the current state; otherwise, false.
110 | ///
111 | Task CanFireAsync(TTrigger trigger);
112 |
113 | ///
114 | /// Gets information about the state machine's configuration (states, transitions, actions) asynchronously.
115 | ///
116 | /// A task that represents the asynchronous operation. The task result contains an object.
117 | Task GetInfoAsync();
118 |
119 | ///
120 | /// Gets the triggers that are permitted to be fired in the current state, considering the provided arguments, asynchronously.
121 | ///
122 | /// Arguments to be passed to the guard functions.
123 | /// A task that represents the asynchronous operation. The task result contains an enumerable collection of permitted triggers.
124 | Task> GetPermittedTriggersAsync(params object[] args);
125 |
126 | ///
127 | /// Gets detailed information about the triggers permitted in the current state, considering the provided arguments, asynchronously.
128 | ///
129 | /// Arguments to be passed to the guard functions.
130 | /// A task that represents the asynchronous operation. The task result contains an enumerable collection of .
131 | Task>> GetDetailedPermittedTriggersAsync(params object[] args);
132 |
133 | ///
134 | /// Gets the triggers that are permitted to be fired in the current state asynchronously (property-like access).
135 | ///
136 | /// A task that represents the asynchronous operation. The task result contains an enumerable collection of permitted triggers.
137 | Task> GetPermittedTriggersPropertyAsync();
138 |
139 | ///
140 | /// Checks if the specified trigger can be fired in the current state asynchronously and returns any unmet guard conditions.
141 | ///
142 | /// The trigger to test.
143 | ///
144 | /// A task that represents the asynchronous operation. The task result is a tuple containing:
145 | /// - bool: True if the trigger can be fired, false otherwise.
146 | /// - ICollection<string>: A collection of descriptions for guard conditions that were not met (if any).
147 | ///
148 | Task<(bool, ICollection)> CanFireWithUnmetGuardsAsync(TTrigger trigger);
149 |
150 | ///
151 | /// Checks if the specified trigger with one argument can be fired in the current state asynchronously.
152 | ///
153 | /// Type of the first trigger argument.
154 | /// The trigger to test.
155 | /// The first argument.
156 | ///
157 | /// A task that represents the asynchronous operation. The task result contains true if the trigger can be fired with the given argument; otherwise, false.
158 | ///
159 | Task CanFireAsync(TTrigger trigger, TArg0 arg0);
160 |
161 | ///
162 | /// Checks if the specified trigger with one argument can be fired in the current state asynchronously and returns any unmet guard conditions.
163 | ///
164 | /// Type of the first trigger argument.
165 | /// The trigger to test.
166 | /// The first argument.
167 | ///
168 | /// A task that represents the asynchronous operation. The task result is a tuple containing:
169 | /// - bool: True if the trigger can be fired, false otherwise.
170 | /// - ICollection<string>: A collection of descriptions for guard conditions that were not met (if any).
171 | ///
172 | Task<(bool, ICollection)> CanFireWithUnmetGuardsAsync(TTrigger trigger, TArg0 arg0);
173 |
174 | ///
175 | /// Checks if the specified trigger with two arguments can be fired in the current state asynchronously.
176 | ///
177 | /// Type of the first trigger argument.
178 | /// Type of the second trigger argument.
179 | /// The trigger to test.
180 | /// The first argument.
181 | /// The second argument.
182 | ///
183 | /// A task that represents the asynchronous operation. The task result contains true if the trigger can be fired with the given arguments; otherwise, false.
184 | ///
185 | Task CanFireAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1);
186 |
187 | ///
188 | /// Checks if the specified trigger with two arguments can be fired in the current state asynchronously and returns any unmet guard conditions.
189 | ///
190 | /// Type of the first trigger argument.
191 | /// Type of the second trigger argument.
192 | /// The trigger to test.
193 | /// The first argument.
194 | /// The second argument.
195 | ///
196 | /// A task that represents the asynchronous operation. The task result is a tuple containing:
197 | /// - bool: True if the trigger can be fired, false otherwise.
198 | /// - ICollection<string>: A collection of descriptions for guard conditions that were not met (if any).
199 | ///
200 | Task<(bool, ICollection)> CanFireWithUnmetGuardsAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1);
201 |
202 | ///
203 | /// Checks if the specified trigger with three arguments can be fired in the current state asynchronously.
204 | ///
205 | /// Type of the first trigger argument.
206 | /// Type of the second trigger argument.
207 | /// Type of the third trigger argument.
208 | /// The trigger to test.
209 | /// The first argument.
210 | /// The second argument.
211 | /// The third argument.
212 | ///
213 | /// A task that represents the asynchronous operation. The task result contains true if the trigger can be fired with the given arguments; otherwise, false.
214 | ///
215 | Task CanFireAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1, TArg2 arg2);
216 |
217 | ///
218 | /// Checks if the specified trigger with three arguments can be fired in the current state asynchronously and returns any unmet guard conditions.
219 | ///
220 | /// Type of the first trigger argument.
221 | /// Type of the second trigger argument.
222 | /// Type of the third trigger argument.
223 | /// The trigger to test.
224 | /// The first argument.
225 | /// The second argument.
226 | /// The third argument.
227 | ///
228 | /// A task that represents the asynchronous operation. The task result is a tuple containing:
229 | /// - bool: True if the trigger can be fired, false otherwise.
230 | /// - ICollection<string>: A collection of descriptions for guard conditions that were not met (if any).
231 | ///
232 | Task<(bool, ICollection)> CanFireWithUnmetGuardsAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1, TArg2 arg2);
233 |
234 | ///
235 | /// Returns a string representation of the state machine's current state and configuration asynchronously.
236 | ///
237 | /// A task that represents the asynchronous operation. The task result contains a string describing the state machine.
238 | Task ToStringAsync();
239 | }
240 |
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine/ManagedCode.Orleans.StateMachine.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | 13
6 | Library
7 | enable
8 | true
9 |
10 |
11 |
12 |
13 | ManagedCode.Orleans.StateMachine
14 | ManagedCode.Orleans.StateMachine
15 | StateMachine implementation on Orleans
16 | managedcode, orleans
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine/Models/OrleansStateInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Orleans;
4 | using Stateless.Reflection;
5 |
6 | namespace ManagedCode.Orleans.StateMachine.Models;
7 |
8 | ///
9 | /// Represents serializable state information for a state in a Stateless state machine,
10 | /// including its substates and superstate, for use with Orleans.
11 | ///
12 | [GenerateSerializer]
13 | public class OrleansStateInfo
14 | {
15 | ///
16 | /// Initializes a new instance of the class from a Stateless .
17 | ///
18 | /// The state information to wrap.
19 | public OrleansStateInfo(StateInfo stateInfo)
20 | {
21 | UnderlyingState = stateInfo.UnderlyingState;
22 |
23 | if (stateInfo.Substates is not null)
24 | Substates = new List(stateInfo.Substates.Select(s => new OrleansStateInfo(s)));
25 |
26 | if (stateInfo.Superstate is not null)
27 | Superstate = new OrleansStateInfo(stateInfo.Superstate);
28 | }
29 |
30 | /// The instance or value this state represents.
31 | [Id(0)]
32 | public object UnderlyingState { get; }
33 |
34 | /// Substates defined for this StateResource.
35 | [Id(1)]
36 | public List Substates { get; private set; }
37 |
38 | /// Superstate defined, if any, for this StateResource.
39 | [Id(2)]
40 | public OrleansStateInfo Superstate { get; private set; }
41 |
42 |
43 | ///
44 | /// Returns a string representation of the underlying state.
45 | ///
46 | /// The string representation of the underlying state or "<null>" if null.
47 | public override string ToString()
48 | {
49 | return UnderlyingState?.ToString() ?? "";
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine/Models/OrleansStateMachineInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Orleans;
5 | using Stateless.Reflection;
6 |
7 | namespace ManagedCode.Orleans.StateMachine.Models;
8 |
9 | ///
10 | /// Represents serializable information about a Stateless state machine,
11 | /// including its initial state, all states, and type information, for use with Orleans.
12 | ///
13 | [GenerateSerializer]
14 | public class OrleansStateMachineInfo
15 | {
16 | ///
17 | /// Initializes a new instance of the class from a Stateless .
18 | ///
19 | /// The state machine information to wrap.
20 | public OrleansStateMachineInfo(StateMachineInfo info)
21 | {
22 | InitialState = new OrleansStateInfo(info.InitialState);
23 | States = info.States.Select(s => new OrleansStateInfo(s)).ToList();
24 |
25 | ArgumentException.ThrowIfNullOrWhiteSpace(info.StateType?.FullName);
26 | ArgumentException.ThrowIfNullOrWhiteSpace(info.TriggerType?.FullName);
27 |
28 | StateType = info.StateType.FullName;
29 | TriggerType = info.TriggerType.FullName;
30 | }
31 |
32 | /// Exposes the initial state of this state machine.
33 | [Id(0)]
34 | public OrleansStateInfo InitialState { get; set; }
35 |
36 | ///
37 | /// Exposes the states, transitions, and actions of this machine.
38 | ///
39 | [Id(1)]
40 | public List States { get; set; }
41 |
42 | /// The type of the underlying state.
43 | ///
44 | [Id(2)]
45 | public string StateType { get; set; }
46 |
47 | /// The type of the underlying trigger.
48 | ///
49 | [Id(3)]
50 | public string TriggerType { get; set; }
51 | }
52 |
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ManagedCode.Orleans.StateMachine": {
4 | "commandName": "Project",
5 | "launchBrowser": true,
6 | "environmentVariables": {
7 | "ASPNETCORE_ENVIRONMENT": "Development"
8 | },
9 | "applicationUrl": "https://localhost:61306;http://localhost:61307"
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/ManagedCode.Orleans.StateMachine/StateMachineGrain.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using ManagedCode.Orleans.StateMachine.Interfaces;
7 | using ManagedCode.Orleans.StateMachine.Models;
8 | using Orleans;
9 | using Stateless;
10 | using Stateless.Graph;
11 |
12 | namespace ManagedCode.Orleans.StateMachine;
13 |
14 | ///
15 | /// Base grain class for integrating a Stateless state machine with Orleans.
16 | /// Provides methods for firing triggers, querying state, and retrieving state machine metadata.
17 | ///
18 | /// The type representing the states.
19 | /// The type representing the triggers.
20 | public abstract class StateMachineGrain : Grain, IStateMachineGrain
21 | {
22 | [NotNull]
23 | protected StateMachine? StateMachine { get; private set; }
24 |
25 | ///
26 | /// Activates the state machine.
27 | ///
28 | public Task ActivateAsync()
29 | {
30 | return StateMachine.ActivateAsync();
31 | }
32 |
33 | ///
34 | /// Deactivates the state machine.
35 | ///
36 | public Task DeactivateAsync()
37 | {
38 | return StateMachine.DeactivateAsync();
39 | }
40 |
41 | ///
42 | /// Fires the specified trigger asynchronously.
43 | ///
44 | /// The trigger to fire.
45 | public Task FireAsync(TTrigger trigger)
46 | {
47 | return StateMachine.FireAsync(trigger);
48 | }
49 |
50 | ///
51 | /// Fires the specified trigger with one argument asynchronously.
52 | ///
53 | public async Task FireAsync(TTrigger trigger, TArg0 arg0)
54 | {
55 | var tp = StateMachine.SetTriggerParameters(trigger);
56 | await StateMachine.FireAsync(tp, arg0);
57 | }
58 |
59 | ///
60 | /// Fires the specified trigger with two arguments asynchronously.
61 | ///
62 | public async Task FireAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1)
63 | {
64 | var tp = StateMachine.SetTriggerParameters(trigger);
65 | await StateMachine.FireAsync(tp, arg0, arg1);
66 | }
67 |
68 | ///
69 | /// Fires the specified trigger with three arguments asynchronously.
70 | ///
71 | public async Task FireAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1, TArg2 arg2)
72 | {
73 | var tp = StateMachine.SetTriggerParameters(trigger);
74 | await StateMachine.FireAsync(tp, arg0, arg1, arg2);
75 | }
76 |
77 | ///
78 | /// Gets the current state asynchronously.
79 | ///
80 | public Task GetStateAsync()
81 | {
82 | return Task.FromResult(StateMachine.State);
83 | }
84 |
85 | ///
86 | /// Determines whether the state machine is in the specified state.
87 | ///
88 | public Task IsInStateAsync(TState state)
89 | {
90 | return Task.FromResult(StateMachine.IsInState(state));
91 | }
92 |
93 | ///
94 | /// Determines whether the specified trigger can be fired.
95 | ///
96 | public Task CanFireAsync(TTrigger trigger)
97 | {
98 | return Task.FromResult(StateMachine.CanFire(trigger));
99 | }
100 |
101 | ///
102 | /// Gets information about the state machine.
103 | ///
104 | public Task GetInfoAsync()
105 | {
106 | return Task.FromResult(new OrleansStateMachineInfo(StateMachine.GetInfo()));
107 | }
108 |
109 | ///
110 | /// Gets the permitted triggers for the current state.
111 | ///
112 | public Task> GetPermittedTriggersAsync(params object[] args)
113 | {
114 | return Task.FromResult(StateMachine.GetPermittedTriggers(args));
115 | }
116 |
117 | ///
118 | /// Gets detailed information about permitted triggers for the current state.
119 | ///
120 | public Task>> GetDetailedPermittedTriggersAsync(params object[] args)
121 | {
122 | return Task.FromResult(StateMachine.GetDetailedPermittedTriggers(args));
123 | }
124 |
125 | ///
126 | /// Gets the permitted triggers property for the current state.
127 | ///
128 | public Task> GetPermittedTriggersPropertyAsync()
129 | {
130 | return Task.FromResult(StateMachine.PermittedTriggers);
131 | }
132 |
133 | ///
134 | /// Determines whether the specified trigger can be fired and returns unmet guard conditions.
135 | ///
136 | public Task<(bool, ICollection)> CanFireWithUnmetGuardsAsync(TTrigger trigger)
137 | {
138 | var result = StateMachine.CanFire(trigger, out var unmetGuards);
139 | return Task.FromResult((result, unmetGuards));
140 | }
141 |
142 | ///
143 | /// Determines whether the specified trigger with one argument can be fired.
144 | ///
145 | public Task CanFireAsync(TTrigger trigger, TArg0 arg0)
146 | {
147 | var tp = StateMachine.SetTriggerParameters(trigger);
148 | return Task.FromResult(StateMachine.CanFire(tp, arg0));
149 | }
150 |
151 | ///
152 | /// Determines whether the specified trigger with one argument can be fired and returns unmet guard conditions.
153 | ///
154 | public Task<(bool, ICollection)> CanFireWithUnmetGuardsAsync(TTrigger trigger, TArg0 arg0)
155 | {
156 | var tp = StateMachine.SetTriggerParameters(trigger);
157 | var result = StateMachine.CanFire(tp, arg0, out var unmet);
158 | return Task.FromResult((result, unmet));
159 | }
160 |
161 | ///
162 | /// Determines whether the specified trigger with two arguments can be fired.
163 | ///
164 | public Task CanFireAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1)
165 | {
166 | var tp = StateMachine.SetTriggerParameters(trigger);
167 | return Task.FromResult(StateMachine.CanFire(tp, arg0, arg1));
168 | }
169 |
170 | ///
171 | /// Determines whether the specified trigger with two arguments can be fired and returns unmet guard conditions.
172 | ///
173 | public Task<(bool, ICollection)> CanFireWithUnmetGuardsAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1)
174 | {
175 | var tp = StateMachine.SetTriggerParameters(trigger);
176 | var result = StateMachine.CanFire(tp, arg0, arg1, out var unmet);
177 | return Task.FromResult((result, unmet));
178 | }
179 |
180 | ///
181 | /// Determines whether the specified trigger with three arguments can be fired.
182 | ///
183 | public Task CanFireAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1, TArg2 arg2)
184 | {
185 | var tp = StateMachine.SetTriggerParameters(trigger);
186 | return Task.FromResult(StateMachine.CanFire(tp, arg0, arg1, arg2));
187 | }
188 |
189 | ///
190 | /// Determines whether the specified trigger with three arguments can be fired and returns unmet guard conditions.
191 | ///
192 | public Task<(bool, ICollection)> CanFireWithUnmetGuardsAsync(TTrigger trigger, TArg0 arg0, TArg1 arg1, TArg2 arg2)
193 | {
194 | var tp = StateMachine.SetTriggerParameters(trigger);
195 | var result = StateMachine.CanFire(tp, arg0, arg1, arg2, out var unmet);
196 | return Task.FromResult((result, unmet));
197 | }
198 |
199 | ///
200 | /// Returns a string representation of the state machine.
201 | ///
202 | public Task ToStringAsync()
203 | {
204 | return Task.FromResult(StateMachine.ToString());
205 | }
206 |
207 | ///
208 | /// Builds the state machine instance.
209 | ///
210 | protected abstract StateMachine BuildStateMachine();
211 |
212 | ///
213 | public override Task OnActivateAsync(CancellationToken cancellationToken)
214 | {
215 | StateMachine = BuildStateMachine();
216 | NotNull(StateMachine, nameof(StateMachine));
217 | return base.OnActivateAsync(cancellationToken);
218 | }
219 |
220 | ///
221 | /// Throws an exception if the provided object is null.
222 | ///
223 | /// The object to check.
224 | /// The name of the object.
225 | private static void NotNull([NotNull] object? obj, string name)
226 | {
227 | if (obj == null)
228 | throw new InvalidOperationException($"{name} cannot be null");
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/Orleans.StateMachine.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCode.Orleans.StateMachine", "ManagedCode.Orleans.StateMachine\ManagedCode.Orleans.StateMachine.csproj", "{78288A9B-7263-47B5-842C-0D1A8B65122F}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCode.Orleans.StateMachine.Tests", "ManagedCode.Orleans.StateMachine.Tests\ManagedCode.Orleans.StateMachine.Tests.csproj", "{9CBCE9A2-D148-41BD-8615-62421EA3ACBB}"
6 | EndProject
7 | Global
8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
9 | Debug|Any CPU = Debug|Any CPU
10 | Release|Any CPU = Release|Any CPU
11 | EndGlobalSection
12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
13 | {78288A9B-7263-47B5-842C-0D1A8B65122F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14 | {78288A9B-7263-47B5-842C-0D1A8B65122F}.Debug|Any CPU.Build.0 = Debug|Any CPU
15 | {78288A9B-7263-47B5-842C-0D1A8B65122F}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | {78288A9B-7263-47B5-842C-0D1A8B65122F}.Release|Any CPU.Build.0 = Release|Any CPU
17 | {9CBCE9A2-D148-41BD-8615-62421EA3ACBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {9CBCE9A2-D148-41BD-8615-62421EA3ACBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {9CBCE9A2-D148-41BD-8615-62421EA3ACBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {9CBCE9A2-D148-41BD-8615-62421EA3ACBB}.Release|Any CPU.Build.0 = Release|Any CPU
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Orleans.StateMachine
2 |
3 | reference:
4 | https://github.com/scottctr/NStateManager
5 | https://github.com/dotnet-state-machine/stateless
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/managedcode/Orleans.StateMachine/3c8782a2a21c00e98d46cc26a0ca951cbfe1916a/logo.png
--------------------------------------------------------------------------------