{"id":13628875,"url":"https://github.com/aabs/ActorSrcGen","last_synced_at":"2025-04-17T04:32:31.470Z","repository":{"id":203891161,"uuid":"710042690","full_name":"aabs/ActorSrcGen","owner":"aabs","description":"ActorSrcGen is a C# Source Generator allowing the conversion of simple C# classes into dataflow compatible pipelines supporting the actor model.","archived":false,"fork":false,"pushed_at":"2024-08-29T01:51:41.000Z","size":194,"stargazers_count":6,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-11-08T19:42:42.489Z","etag":null,"topics":["actor-model","asynchronous-programming","csharp-sourcegenerator","high-performance-computing","parallel-programming","source-generator","sourcegenerator","tpl-dataflow"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aabs.png","metadata":{"files":{"readme":"ReadMe.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-25T22:33:07.000Z","updated_at":"2024-08-29T01:51:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"ca925e7d-5400-4971-adb8-e92d5c93fe4c","html_url":"https://github.com/aabs/ActorSrcGen","commit_stats":null,"previous_names":["aabs/dataflowsrcgen","aabs/actorsrcgen"],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aabs%2FActorSrcGen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aabs%2FActorSrcGen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aabs%2FActorSrcGen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aabs%2FActorSrcGen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aabs","download_url":"https://codeload.github.com/aabs/ActorSrcGen/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249315999,"owners_count":21249871,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actor-model","asynchronous-programming","csharp-sourcegenerator","high-performance-computing","parallel-programming","source-generator","sourcegenerator","tpl-dataflow"],"created_at":"2024-08-01T22:00:58.819Z","updated_at":"2025-04-17T04:32:30.808Z","avatar_url":"https://github.com/aabs.png","language":"C#","readme":"﻿# Welcome To ActorSrcGen \n \nActorSrcGen is a C# Source Generator that converts simple C# classes into TPL Dataflow-compatible pipelines. It simplifies working with TPL Dataflow by generating boilerplate code to handle errors without interrupting the pipeline, ideal for long-lived processes with ingesters that continually pump messages into the pipeline.\n\n\n## Getting Started\n\n1.\t**Install the package**:\n\n    ```shell\n    dotnet add package ActorSrcGen\n    ```\n\n2.\t**Declare the pipeline class**:\n\n    ```csharp\n    [Actor]\n    public partial class MyPipeline\n    {\n    }\n    ```\n\n    The class must be `partial` to allow the source generator to add boilerplate code.\n\n    If you are using Visual Studio, you can see the generated part of the code under the\n    ActorSrcGen analyzer:\n\n    ![File1](doc/file1.png)\n\n3.\t**Create ingester functions**:\n\n    ```csharp\n    [Ingest(1)]\n    [NextStep(nameof(DoSomethingWithRequest))]\n    public async Task\u003cstring\u003e ReceivePollRequest(CancellationToken cancellationToken)\n    {\n        return await GetTheNextRequest();\n    }\n    ```\n\n    Ingesters define a `Priority` and are visited in priority order. If no messages are available, the pipeline sleeps for a second before retrying.\n\n4.\t**Implement pipeline steps**:\n\n    ```csharp\n    [FirstStep(\"decode incoming poll request\")]\n    [NextStep(nameof(ActOnTheRequest))]\n    public PollRequest DecodeRequest(string json)\n    {\n        Console.WriteLine(nameof(DecodeRequest));\n        var pollRequest = JsonSerializer.Deserialize\u003cPollRequest\u003e(json);\n        return pollRequest;\n    }\n    ```\n\n    The first step controls the pipeline's interface. Implement additional steps as needed, ensuring input and output types match.\n\n1. Now **implement other steps** are needed in the pipeline.  The outputs and input types \n    of successive steps need to match.\n\n    ```csharp\n    [Step]\n    [NextStep(nameof(DeliverResults))]\n    public PollResults ActOnTheRequest(PollRequest req)\n    {\n        Console.WriteLine(nameof(ActOnTheRequest));\n        var result = SomeApiClient.GetTheResults(req.Id);\n        return result;\n    }\n    ```\n\n5.\t**Define the last step**:\n\n    ```csharp\n    [LastStep]\n    public bool DeliverResults(PollResults res)\n    {\n        return myQueue.TryPush(res);\n    }\n    ```\n\n\n6.\t**Generated code example**:\n\n    ```csharp\n    using System.Threading.Tasks.Dataflow;\n    using Gridsum.DataflowEx;\n\n    public partial class MyActor : Dataflow\u003cstring, bool\u003e, IActor\u003c string \u003e\n    {\n\n\t    public MyActor(DataflowOptions dataflowOptions = null) : base(DataflowOptions.Default)\n\t    {\n            _DeliverResults = new TransformBlock\u003cPollResults,bool\u003e(         (PollResults x) =\u003e {\n                try\n                {\n                    return DeliverResults(x);\n                }\n                catch\n                {\n                    return default;\n                }\n            },\n                new ExecutionDataflowBlockOptions() {\n                    BoundedCapacity = 1,\n                    MaxDegreeOfParallelism = 1\n            });\n            RegisterChild(_DeliverResults);\n\n            _ActOnTheRequest = new TransformBlock\u003cPollRequest,PollResults\u003e(         (PollRequest x) =\u003e {\n                try\n                {\n                    return ActOnTheRequest(x);\n                }\n                catch\n                {\n                    return default;\n                }\n            },\n                new ExecutionDataflowBlockOptions() {\n                    BoundedCapacity = 1,\n                    MaxDegreeOfParallelism = 1\n            });\n            RegisterChild(_ActOnTheRequest);\n\n            _DecodeRequest = new TransformBlock\u003cstring,PollRequest\u003e(         (string x) =\u003e {\n                try\n                {\n                    return DecodeRequest(x);\n                }\n                catch\n                {\n                    return default;\n                }\n            },\n                new ExecutionDataflowBlockOptions() {\n                    BoundedCapacity = 1,\n                    MaxDegreeOfParallelism = 1\n            });\n            RegisterChild(_DecodeRequest);\n\n            _ActOnTheRequest.LinkTo(_DeliverResults, new DataflowLinkOptions { PropagateCompletion = true });\n            _DecodeRequest.LinkTo(_ActOnTheRequest, new DataflowLinkOptions { PropagateCompletion = true });\n\t        }\n\n            TransformBlock\u003cPollResults,bool\u003e _DeliverResults;\n            TransformBlock\u003cPollRequest,PollResults\u003e _ActOnTheRequest;\n            TransformBlock\u003cstring,PollRequest\u003e _DecodeRequest;\n            public override ITargetBlock\u003cstring \u003e InputBlock { get =\u003e _DecodeRequest ; }\n            public override ISourceBlock\u003c bool \u003e OutputBlock { get =\u003e _DeliverResults; }\n            public bool Call(string input) =\u003e InputBlock.Post(input);\n            public async Task\u003cbool\u003e Cast(string input) =\u003e await InputBlock.SendAsync(input);\n    \n            public async Task\u003cbool\u003e AcceptAsync(CancellationToken cancellationToken)\n            {\n                try\n                {\n                    var result = await _DeliverResults.ReceiveAsync(cancellationToken);\n                    return result;\n                }\n                catch (OperationCanceledException operationCanceledException)\n                {\n                    return await Task.FromCanceled\u003cbool\u003e(cancellationToken);\n                }\n            }\n\n          public async Task Ingest(CancellationToken ct)\n          {\n            // start the message pump\n            while (!ct.IsCancellationRequested)\n            {\n              var foundSomething = false;\n              try\n              {\n                // cycle through ingesters IN PRIORITY ORDER.\n                {\n                    var msg = await ReceivePollRequest(ct);\n                    if (msg != null)\n                    {\n                        Call(msg);\n                        foundSomething = true;\n                        // then jump back to the start of the pump\n                        continue;\n                    }\n                }\n\n                if (!foundSomething) \n                    await Task.Delay(1000, ct);\n              }\n              catch (TaskCanceledException)\n              {\n                // if nothing was found on any of the receivers, then sleep for a while.\n                continue;\n              }\n              catch (Exception e)\n              {\n                // _logger.LogError(e, \"Exception suppressed\");\n              }\n            }\n          }\n        }\n    ```\n\n\n7.\t**Using the pipeline**:\n\n    ```csharp\n    var actor = new MyActor(); // this is your pipeline\n\n    try\n    {\n        // call into the pipeline synchronously\n        if (actor.Call(\"\"\"\n                       { \"something\": \"here\" }\n                       \"\"\"))\n            Console.WriteLine(\"Called Synchronously\");\n\n        // stop the pipeline after 10 secs\n        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));\n\n        // kick off an endless process to keep ingesting input into the pipeline\n        var t = Task.Run(async () =\u003e await actor.Ingest(cts.Token), cts.Token);\n\n        // consume results from the last step via the AcceptAsync method\n        while (!cts.Token.IsCancellationRequested)\n        {\n            var result = await actor.AcceptAsync(cts.Token);\n            Console.WriteLine($\"Result: {result}\");\n        }\n\n        await t; // cancel the message pump task\n        await actor.SignalAndWaitForCompletionAsync(); // wait for all pipeline tasks to complete\n    }\n    catch (OperationCanceledException _)\n    {\n        Console.WriteLine(\"All Done!\");\n    }\n    ```\n\n\n## Benefits\n- Simplifies TPL Dataflow usage: Automatically generates boilerplate code.\n- Concurrency: Efficient use of multiple CPU cores.\n- Fault tolerance: Errors in pipeline steps are trapped and handled.\n- Encapsulation: Easier to reason about and test code.\n\n\n## Acknowledgements\n\nBuilt on [DataflowEx](https://github.com/gridsum/DataflowEx) and [Bnaya.SourceGenerator.Template](https://github.com/bnayae/Bnaya.SourceGenerator.Template).\n","funding_links":[],"categories":["Content","Source Generators"],"sub_categories":["141. [ActorSrcGen](https://ignatandrei.github.io/RSCG_Examples/v2/docs/ActorSrcGen) , in the [Actor](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#actor) category","Patterns"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faabs%2FActorSrcGen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faabs%2FActorSrcGen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faabs%2FActorSrcGen/lists"}