{"id":28033030,"url":"https://github.com/gavinnl/pseudonix","last_synced_at":"2026-04-28T00:33:03.725Z","repository":{"id":290397406,"uuid":"966369063","full_name":"GavinNL/PseudoNix","owner":"GavinNL","description":"An embeddable Linux-like system powered by coroutines.","archived":false,"fork":false,"pushed_at":"2025-05-20T02:04:14.000Z","size":437,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-20T03:22:27.476Z","etag":null,"topics":["coroutines","cpp20","cpp20-library","embedded","shell"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GavinNL.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2025-04-14T20:19:07.000Z","updated_at":"2025-05-14T14:33:04.000Z","dependencies_parsed_at":"2025-04-28T16:50:48.339Z","dependency_job_id":"60629eb7-7cc2-4d02-aa24-bb29a1f0c968","html_url":"https://github.com/GavinNL/PseudoNix","commit_stats":null,"previous_names":["gavinnl/pseudonix"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/GavinNL/PseudoNix","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinNL%2FPseudoNix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinNL%2FPseudoNix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinNL%2FPseudoNix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinNL%2FPseudoNix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GavinNL","download_url":"https://codeload.github.com/GavinNL/PseudoNix/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GavinNL%2FPseudoNix/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259519825,"owners_count":22870370,"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":["coroutines","cpp20","cpp20-library","embedded","shell"],"created_at":"2025-05-11T09:10:25.956Z","updated_at":"2026-04-28T00:33:03.685Z","avatar_url":"https://github.com/GavinNL.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PseudoNix\n\n\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/GavinNL/PseudoNix/.github%2Fworkflows%2Fcmake-multi-platform.yml?branch=main\u0026style=for-the-badge\u0026logo=github\u0026label=main\" \nhref=\"https://github.com/GavinNL/PseudoNix/actions?query=branch%3Amain\" alt=\"Main GitHub Actions Workflow Status\"\u003e \u003cimg src=\"https://img.shields.io/github/actions/workflow/status/GavinNL/PseudoNix/.github%2Fworkflows%2Fcmake-multi-platform.yml?branch=dev\u0026style=for-the-badge\u0026logo=github\u0026label=dev\" \nhref=\"https://github.com/GavinNL/PseudoNix/actions?query=branch%3Adev\"  alt=\"Dev GitHub Actions Workflow Status\"\u003e\n\nPseudoNix is an embeddable header-only, Linux-like environment you can integrate\ndirectly into your project to provide concurrent process like behaviour.\n\n[Live Demo Using ImGui](https://filedn.eu/l0rnKqYfU3SSI61WTa9844f/PseudoNix/index.html)\n\n## Dependendices\n\n* **Required**\n  * C++20 Compiler\n  * [readerwriterqueue](https://github.com/cameron314/readerwriterqueue) by cameron314 (available on Conan)\n  * [concurrentqueue](https://github.com/cameron314/concurrentqueue) by cameron314 (available on Conan)\n* **Optional**\n  * [libarchive](https://github.com/libarchive/libarchive) (available on Conan) - required to mount tar/tar.gz files\n\n## Compiling the Examples\n\nEdit the top level `CMakeLists.txt` and change the project name. \n\n```bash\ncd SRC_FOLDER\n\n# execute conan to install the packages you need\nconan install conanfile.py --build missing -of=build\n\n# Run cmake\ncmake --preset conan-release .\n\n```\n\n## Usage In your Project\n\nIf you are using the Conan Package Manager, you can add the following to your dependences list:\n\n```python\n    self.requires(\"readerwriterqueue/1.0.6\")\n    self.requires(\"concurrentqueue/1.0.4\")\n\n    # Optional: Allows mounting tar/tar.gz files\n    self.requires(\"libarchive/3.7.9\")\n\n    # Optional: Provides a working GUI terminal emulator\n    #           for Imgui applications\n    self.requires(\"imgui/1.91.8-docking\")\n```\n\nAdd this repo as a submodule and then add it as a subdirectory\n\n```cmake\nfind_package(readerwriterqueue REQUIRED)\nfind_package(concurrentqueue REQUIRED)\n\nadd_subdirectory(third_party/PseudoNix)\n\ntarget_link_libraires(myapp PseudoNix::PseudoNix readerwriterqueue::readerwriterqueue concurrentqueue::concurrentqueue)\n```\n\n## How It Works\n\nThe PseudoNix::System acts like a fully contained Linux system and scheduler\nwhich can execute processes concurrently. A process is a coroutine which can be\nadded to the system to be executed. The coroutines that are added to the system\nare not automatically executed. When `system.executeAll()` is called, each\ncoroutine is resumed one at a time until all of them have been resumed.\n\nCoroutines run on a single thread by design in the order of their PID number.\n\nThe coroutines provide a `input/output` stream which can be written to. This is\nsimlar to the standard input/output streams, but instead of writing to the\nconsole, it writes to memory. This way the output of one process can be sent to\nthe input of another, just like on Linux.\n\n## Use Cases\n\nThis library was built because I needed a way to interface with a custom CAD\napplication I was building. I needed a shell interface where I could execute\ncommands and probe information about the system. After add more and more\nfeatures, I decided to turn it into its own library.\n\n### Features\n\n - Bash-like shell interface\n - Simple shell scripts\n - Define your own process coroutines similar to a linux process\n - Run your processes within the system's scheduler\n - Chain processes together `proc1 | proc2` just like in Linux\n - Signal running proccess to terminate using the `kill` command\n - ImGui Terminal Window\n - Thread Pools to run processes outside of the MAIN Task Queue\n\n### Future Development\n\n - [x] Virtual Filesystem\n   - [x] Mounting Archives\n   - [x] Mounting Archives from Memory\n - [x] Better bash-features (if statements, loops)\n   - [x] If-statements\n   - [x] While-Loops\n   - [x] For-Loops\n   - [x] breaks/continue\n - [ ] Pausing processes\n - [ ] More GNU core-utils like functions\n   - [ ] head/tail\n   - [ ] grep\n\n\n## Examples\n\n### Example 1: Basic Usage\n\n```c++\n#include \u003cPseudoNix/System.h\u003e\n\n\nPseudoNix::System::task_type my_custom_function(PseudoNix::System::e_type ctrl)\n{\n    auto sleep_time = std::chrono::milliseconds(250);\n    for(int i=0;i\u003c10;i++)\n    {\n        std::cout \u003c\u003c std::format(\"[{}] Counter: {}\", ctrl-\u003eargs[1], i) \u003c\u003c std::endl;\n\n        // yield some time back to the scheduler\n        // so that other processes can execute\n        co_await ctrl-\u003eawait_yield_for(sleep_time);\n    }\n    co_return 0;\n}\n\nint main()\n{\n    using namespace PseudoNix;\n    // The first thing we need to do is create\n    // the instance of the mini linux system\n    //\n    System M;\n    // add our coroutine to the list of functions to be\n    // called\n    M.setFunction(\"mycustomfunction\", my_custom_function);\n\n    // run 3 instances of the coroutine using different input\n    // arguments\n    M.spawnProcess({\"mycustomfunction\", \"alice\"});\n    M.spawnProcess({\"mycustomfunction\", \"bob\"});\n    M.spawnProcess({\"mycustomfunction\", \"charlie\"});\n\n    // Execute the main task queue\n    while(M.taskQueueExecute())\n    {\n        // sleep for 1 millisecond so we're not\n        // doing a busy loop\n        std::this_thread::sleep_for(std::chrono::milliseconds(1));\n    }\n\n    return 0;\n}\n\n```\n\n### Example 2: Using the Input/Output Streams\n\nThis is a slightly stripped down version of the `main.cpp` example. Unlike in\nExample 1, where we wrote directly to std::cout, we are instead going to write\nto the output stream of the process.\n\nThe output stream will be piped into another process which will write the data\nto std::cout.\n\n```c++\n#include \u003cPseudoNix/System.h\u003e\n\nPseudoNix::System::task_type my_custom_function(PseudoNix::System::e_type ctrl)\n{\n    auto sleep_time = std::chrono::milliseconds(250);\n    for(int i=0;i\u003c10;i++)\n    {\n        // write to the process's output stream\n        *ctrl-\u003eout \u003c\u003c std::format(\"[{}] Counter: {}\\n\", ctrl-\u003eargs[1], i);\n        co_await ctrl-\u003eawait_yield_for(sleep_time);\n    }\n    co_return 0;\n}\n\nint main()\n{\n    PseudoNix::System M;\n\n    // add our coroutine to the list of functions to be\n    // called\n    M.setFunction(\"mycustomfunction\", my_custom_function);\n\n    // We can manually create a pipeline. This will\n    // pipe the output of one function into the input of another\n    // just like in linux:  mycustomfunction | to_std_cout\n    //\n    // The to_std_cout function is provided for you\n    // It simply takes whatever is in its input buffer\n    // and writes it to std::cout\n    M.spawnPipelineProcess({\n            {\"mycustomfunction\", \"alice\"},\n            {\"to_std_cout\"}\n    });\n\n    // Execute the main task queue\n    while(M.taskQueueExecute())\n    {\n        std::this_thread::sleep_for(std::chrono::milliseconds(1));\n    }\n\n    return 0;\n}\n```\n\n\n### Example 3: Using the Shell Process\n\nA `shell` process, similar to bash, is provided for you. This shell process can\nbe used to give you an actual command prompt entry into the PseudoNix system and\nlet you launch commands.\n\nAdditionally, if you are building a command line application, you will need the\n`launcher` process. The `launcher` reads data from std::cin, and pipes that data\ninto a new process. It then takes the output and writes that to std::cout.\nWithout this, the shell will not be able to write anything to your terminal\noutput.\n\n\n```c++\n#include \u003cPseudoNix/System.h\u003e\n#include \u003cPseudoNix/Shell.h\u003e\n#include \u003cPseudoNix/Launcher.h\u003e\n\nint main()\n{\n    PseudoNix::System M;\n\n    // register the shell function\n    M.setFunction(\"sh\", std::bind(PseudoNix::shell_coro, std::placeholders::_1, PseudoNix::ShellEnv{}));\n    M.setFunction(\"launcher\", PseudoNix::launcher_coro);\n\n    auto launcher_pid = M.spawnProcess({\"launcher\", \"sh\"});\n\n    // Execute the main task queue\n    while(M.taskQueueExecute())\n    {\n        std::this_thread::sleep_for(std::chrono::milliseconds(1));\n    }\n\n    return 0;\n}\n```\n\n#### Special Shell Features\n\nPsuedonix provides a default **shell** process that can be used as a starting\npoint to create interactivity. This shell behaves behaves similar to bash,\nallowing many features such as:\n\nIt supports custom command registration and mimics familiar shell behavior\nincluding environment variables, command substitution, logical operators `\u0026\u0026,\n||`, and output redirection `cmd1 | cmd2`. Perfect for building scriptable,\nextensible CLI experiences right into your application.\n\nPseudoNix allows you to register your own coroutine functions so that they can\nbe called within the system.\n\n * Logical commands: `true \u0026\u0026 echo true || echo false`\n * Setting environment variables: `VAR=VALUE`\n * Variable substitution: `echo hello ${VAR}`\n * Passing variables to commands: `VAR=value env`\n * Executing in the background: `sleep 10 \u0026\u0026 echo hello world \u0026`\n * Command substitution: `echo Running for: $(uptime) ms`\n * Call your own coroutine functions\n\n**NOTE**: The `shell` process is not a full bash interpreter. It does not\nprovide many of the features. It was inteded to be a simple interface into the\nPseudoNix system. The following bash features are not provided, but may be\nincluded in the future\n\n  * if statements\n  * loops\n  * functions\n\n\n#### Default Functions\n\nHere is a list of commands that are provided by default, mostly for testing purposes.\nSee the examples below to define your own.\n\n| name           | Description                                                      |\n| -------------- | ---------------------------------------------------------------- |\n| bgrunner       | Spawn a background thread to process a Task Queue                |\n| cat            | Concatenates files to standard output                            |\n| cd             | Changes the current working directory                            |\n| cp             | Copies files and directories                                     |\n| echo           | Prints arguments to standard output                              |\n| env            | Prints out all environment variables                             |\n| exit           | Exits the shell                                                  |\n| export         | Exports environment variables to new processes                   |\n| exported       | Prints exported environment variables                            |\n| false          | Returns with exit code 1                                         |\n| help           | Shows the list of commands                                       |\n| io_info        | Shows IO pointers                                                |\n| kill           | Terminate a process                                              |\n| launcher       | Launches another process and redirects stdin/out to the process. |\n| ls             | Lists files and directories                                      |\n| mkdir          | Create directories                                               |\n| mount          | Mounts host filesystems inside the VFS                           |\n| ps             | Shows the current process list                                   |\n| pwd            | Prints the current working directory                             |\n| queue          | Create/List/Destroy task queues                                  |\n| queueHopper    | Example process that hops to different task queues               |\n| rev            | Reverses the input                                               |\n| rm             | Removes files and directories                                    |\n| sh             | The default shell                                                |\n| signal         | Send a signal to a process                                       |\n| sleep          | Pauses for NUMBER seconds                                        |\n| spawn          | Spawns N instances of the same process                           |\n| to_std_cout    | Pipes process output to standard output                          |\n| touch          | Create files                                                     |\n| true           | Returns with exit code 0                                         |\n| umount         | Unmounts a host filesystem                                       |\n| uptime         | Number of milliseconds since started                             |\n| wc             | Counts the number of characters                                  |\n| yes            | Keeps printing y to stdout until interrupted                     |\n\n### Example 4: Integrating with GUI\n\nPsuedoNix was originally built to be integrated into a game engine I was building, so\nwas designed to be easily integrated into a GUI (eg: ImGui)\n\nA very simple ImGui Terminal emulator process has been created for you to use. \n\nSee the [terminal](examples/terminal.cpp) example\n\n```c++\n#include \u003cPseudoNix/System.h\u003e\n#include \u003cPseudoNix/Shell.h\u003e\n#include \u003cPseudoNix/ImGuiTerminal.h\u003e\n\nint main()\n{\n    PseudoNix::System system;\n    system.setFunction(\"sh\", std::bind(PseudoNix::shell_coro, std::placeholders::_1, PseudoNix::ShellEnv{}));\n    system.setFunction(\"term\", PseudoNix::terminalWindow_coro);\n\n    # Spawn the Imgui Terminal\n    system.spawnProcess({\"term\", \"sh\"});\n\n    // somewhere in your imgui draw loop, you can\n    // execute the system\n    while(true)  {\n        ...\n        ImGui::BeginFrame();\n\n        M.taskQueueExecute();\n\n        ImGui::EndFrame();\n        ...\n    }\n}\n```\n\n### Example 5: Guessing Game\n\nHere's an example of creating a simple guessing game within the PsuedoNix\nsystem. Remember that **all process functions happen concurrently, but on a\nsingle thread**. So to be able to run concurrently, processes that would\nnormally block at a location, should use specific co-routine awaiters provided by\nthe ProcessControl object.\n \n```c++\n\nint main()\n{\n    PseudoNix::System M;\n\n    M.setFunction(\"guess\", [](PseudoNix::System::e_type ctrl) -\u003e PseudoNix::System::task_type\n    {\n        // Macro to define a few variables such as\n        // IN, OUT, ENV, SYSTEM, ARGS, PID\n        PSEUDONIX_PROC_START(ctrl);\n\n        std::string input;\n        uint32_t random_number = std::rand() % 100 + 1;\n        OUT \u003c\u003c std::format(\"I have chosen a number between 1-100. Can you guess what it is?\\n\");\n\n        while(true)\n        {\n            std::string line;\n\n            // HANDLE_AWAIT_BREAK_ON_SIGNAL is a macro that looks at the return type of the\n            // Awaiter (a signal code), and breaks the while loop\n            // exit code. It will exit if the code is SIG_TERM or SIG_INT\n            //\n            // This is where Ctrl-C and Sig-kills are handled\n            HANDLE_AWAIT_BREAK_ON_SIGNAL(co_await ctrl-\u003eawait_read_line(ctrl-\u003ein, line), ctrl)\n\n            uint32_t guess = 0;\n\n            if(std::errc() != std::from_chars(line.data(), line.data() + line.size(), guess).ec)\n            {\n                OUT \u003c\u003c std::format(\"invalid entry: {}\\n\", line);\n                OUT \u003c\u003c std::format(\"Guess Again: \\n\");\n                continue;\n            }\n\n            if(guess \u003e random_number)\n            {\n                OUT \u003c\u003c std::format(\"Too High!\\n\");\n            }\n            else if(guess \u003c random_number)\n            {\n                OUT  \u003c\u003c std::format(\"Too Low!\\n\");\n            }\n            else\n            {\n                OUT \u003c\u003c std::format(\"Awesome! You guessed the correct number: {}!\\n\", random_number);\n                OUT \u003c\u003c std::format(\"Exiting\\n\");\n                co_return 0;\n            }\n        }\n\n        co_return 0;\n    });\n}\n```\n\n### Example 6: Multiple Task Queues\n\nProcesses in the PseudoNix System are executed on a Task Queue. There is a\n`\"MAIN\"` queue which is executed when you call  `system.taskQueueExecute()`. \nBy default all tasks will be executed on that queue.\n\nYou can create different task queues, which can be executed at different times\nin your application. For example, you can have a task queue that executes during\nyour Physics portion of your game engine, and one that runs during the Render\nPass of your graphics pipeline.\n\nYour processes can switch to different task queues by calling `await_yield` and\npassing in the name of the queue you want to continue to execute.\n\n```c++\n    co_await ctrl-\u003eawait_yield(\"RENDERPASS_QUEUE\");\n```\n\nAdditional Task Queues can be created using the\n`system.taskQueueCreate(name_str)` function.\n\nThe `queueHopper` function is created by default as an example, but the code is\nshown below with some of the validation checks removed.\n\n```c++\nint main()\n{\n    PseudoNix::System M;\n    M.setFunction(\"sh\", std::bind(PseudoNix::shell_coro, std::placeholders::_1, ShellEnv{}));\n    M.setFunction(\"launcher\", PseudoNix::launcher_coro);\n\n    M.setFunction(\"queueHopper\", [](e_type ctrl) -\u003e task_type\n    {\n        PSEUDONIX_PROC_START(ctrl);\n        using namespace std::chrono_literals;\n\n        std::string TASK_QUEUE_NAME = ARGS[1];\n\n        // the QUEUE variable defined by PSEUDONIX_PROC_START(ctrl)\n        // tells you what queue this process is being executed on\n        COUT \u003c\u003c std::format(\"On {} queue\\n\", QUEUE);\n\n        for(int i=0;i\u003c10;i++)\n        {\n            // Wait 250ms then hop onto the other task queue\n            HANDLE_AWAIT_INT_TERM(co_await ctrl-\u003eawait_yield_for(250ms, TASK_QUEUE_NAME), ctrl);\n\n            COUT \u003c\u003c std::format(\"On {} queue\\n\", QUEUE);\n\n            // hop back onto the default queue\n            HANDLE_AWAIT_INT_TERM(co_await ctrl-\u003eawait_yield_for(250ms, PseudoNix::System::DEFAULT_QUEUE), ctrl);\n\n            COUT \u003c\u003c std::format(\"On {} queue\\n\", QUEUE);\n        }\n\n        co_return 0;\n    });\n\n    M.taskQueueCreate(\"PRE_MAIN\");\n    M.taskQueueCreate(\"POST_MAIN\");\n\n    M.spawnPipelineProcess({\n            {\"launcher\", \"sh\"}\n    });\n\n    while(true)\n    {\n        // execute each task queue in a specific order\n        auto total_tasks =  M.taskQueueExecute(\"PRE_MAIN\");\n        total_tasks += M.taskQueueExecute();\n        total_tasks += M.taskQueueExecute(\"POST_MAIN\");\n        if(total_tasks == 0) \n            break;\n\n        // sleep for 1 millisecond so we're not\n        // doing a busy loop\n        std::this_thread::sleep_for(std::chrono::milliseconds(1));\n    }\n\n}\n```\n\n\n## Signal Handlers, Exiting Gracefully and Traps\n\nIn Linux, you can signal a process to interrupt, usually with Ctrl+C. This will\ntell the process that it should stop what its doing and react to the event, or\nexit the program. To be able to handle this behaviour in your coroutines, when\nyou co_await, you can use a handy macro `HANDLE_AWAIT_BREAK_ON_SIGNAL` to\nread the output of the co_await and break out of the loop if it receives a\nsignal. \n\nIf you call `signal \u003cPID\u003e 2`, or `signal \u003cPID\u003e 15` from the shell process (the \n2 and the 15 are linux SIG_INT and SIG_TERM), it will tell your coroutine to\nexit its while loop. Since we reacted to an interrupt signal, it exited the loop and\nexited gracefully, printing out `This is a graceful exit`.\n\nBut if we call `kill \u003cPID\u003e`, it will not send a signal, instead if will flag the\ncoroutine to be removed from the scheduler. You can use the `PSEUDONIX_TRAP`\nto create a deferred block that will be executed with the coroutine is destroyed.\nThis is useful if your coroutine allocated any memory or needs to do some additional\ncleanup.\n\nTry running example4.cpp, in the shell process try executing `mycustomfunction` \nin the background. Then use `ps` to find its PID, and either call `signal \u003cPID\u003e 2`\nor `kill \u003cPID\u003e`\n\n\n\n```c++\nPseudoNix::System::task_type mycustomfunction(PseudoNix::System::e_type ctrl)\n{\n    PSEUDONIX_PROC_START(ctrl);\n    auto sleep_time = std::chrono::milliseconds(250);\n\n    PSEUDONIX_TRAP {\n        // This will be called even if you call \"kill\"\n        // on the pid\n        OUT \u003c\u003c std::format(\"This is executed on cleanup.\");\n    };\n\n    int i=0;\n    while(true)\n    {\n        OUT \u003c\u003c std::format(\"Counter: {}\\n\", i++);\n\n        // await for the awaiter to signal\n        // if it does, break the while loop if\n        // it returned any of the known signals:\n        //  sig_terminate, sig_interrupt\n        HANDLE_AWAIT_BREAK_ON_SIGNAL(co_await ctrl-\u003eawait_yield_for(sleep_time), ctrl);\n    }\n\n    // this will only be called if the while loop exits\n    // properly by reacting to a signal, either:\n    // signal PID 2\n    // signal PID 15\n    OUT \u003c\u003c std::format(\"This a graceful exit\\n\");\n    co_return 0;\n}\n```\n\n## Process Control \n\nThe ProcessControl object is passed into your function as the input argument. \nThese are similar to any linux process. \n\nYou can use the `PSEUDONIX_PROC_START(ctrl)` macro, so define some references\nthat are easy to access:\n\n```c++\nM.setFunction(\"guess\", [](PseudoNix::System::e_type ctrl) -\u003e PseudoNix::System::task_type\n{\n    PSEUDONIX_PROC_START(ctrl);\n\n    ctrl-\u003eargs; // vector of strings, your command line arguments\n    ctrl-\u003ein;  // the standard input stream\n    ctrl-\u003eout; // the standard output stream\n    ctrl-\u003eenv; // the environment variables\n\n    // COUT - Reference to the output stream\n    // CIN  - Reference to the input stream\n    // ARGS - The command line arguments to this process\n    // ENV  - The environment variable map\n\n    // PID      - the PID number for this process\n    // EXPORTED - A map of all exported variables\n    // QUEUE    - string indicating the name of the queue that the \n    //            process is running on\n    // CWD      - The current working directory of the process\n\n    // SYSTEM           - reference to the pseudonix system/filesystem\n    // LAST_SIGNAL      - The last signal that was received\n    // PARENT_SHELL_PID - the PID of the parent shell process\n\n    co_return 0;\n});\n```\n\n## Coroutine Awaiters\n\nThe Coroutine Awaiters are used to pause your process and yield the time to\nanother process in the scheduler until some event has occured.\n\nThe following is the simplest awaiter, which just pauses and waits until the\nscheduler resumes this process at a later time.\n\n```c++\nauto await_result = co_await ctrl-\u003eawait_yield();\n```\n\nThe awaiters return an `AwaitResult` enum which tells you what you should do. If\nit returns `AwaitResult::SUCCESS`, then the awaiter returned properly.\n\nOther options are `AwaitResult::SIGNAL_INTERRUPT` and\n`AwaitResult::SIGNAL_TERMINATE`, which mean the process was asked to interrupt\nitself (Ctrl+C in bash), or terminate itself (kill PID).\n\nIn most cases, you would want to exit your process, unless you have some custom\nbehaviour. The `shell` and `launcher` have custom behaviour\n\n```c++\nswitch(co_await ctrl-\u003eawait_yield())\n{\ncase AwaiterResult::SIGNAL_INTERRUPT:  { co_return 1;}\ncase AwaiterResult::SIGNAL_TERMINATE: { co_return 1;}\ndefault: break;\n}\n```\n\nA macro has been created so that you can do this automatically. The following is\na very simple process that just prints out \"hello world\" continuously until you\ninterrupt or kill the process\n\n```c++\nM.setFunction(\"hello\", [](PseudoNix::System::e_type ctrl) -\u003e PseudoNix::System::task_type\n{\n    int i=0;\n    while(true)\n    {\n        *ctrl-\u003eout \u003c\u003c std::format(\"Hello world: {}\\n\", i++);\n        HANDLE_AWAIT_INT_TERM(co_await ctrl-\u003eawait_yield(), ctrl);\n    }\n\n    co_return 0;\n});\n```\n\nThe following is a list of awaiters that can be used \n\n| Awaiter                                   | Description\n| ----------------------------------------- |------------------------------------------ |\n| ctrl-\u003eawait_yield()                       | Pauses until the next schedule            |\n| ctrl-\u003eawait_yield_for(duration)           | Sleeps for a certain amount of time       |\n| ctrl-\u003eawait_has_data(ctrl-\u003ein)            | Waits until there is data in the stream   |\n| ctrl-\u003eawait_read_line(ctrl-\u003ein, line_str) | Waits until a line has been read          |\n| ctrl-\u003eawait_finished(pid)                 | Waits until another process has completed |\n\n\n## Thread Pools\n\nProcesses started in the PseudoNix system are always run on a single thread and\nonly when the `executeTaskQueue` is called. This is so that the processes\nexecute at known times within your application.\n\nYou may have a process that takes an exceptionally long time to load and may not\nhave a convenient way of yielding, for example, loading a large asset into\nmemory. You may want to load this asset in a background thread. \n\nOne way of achieving this is to handle the background loading yourself. \n\n```c++\nM.setFunction(\"loadAsset\", [](e_type ctrl) -\u003e task_type\n{\n    PSEUDONIX_PROC_START(ctrl);\n\n    std::filesystem::path p(ARGS[1]);\n\n    auto fut = std::async(std::launch::async, [\u0026]()\n    {\n        // load p from the filesystem in a background\n        // thread\n    });\n\n    while(fut.wait_for(std::chrono::seconds(0))==std::future_status::timeout )\n    {\n        co_await ctrl-\u003eawait_yield();\n    }\n\n    auto asset = fut.get();\n    // do what you need with asset\n    co_return 0;\n});\n```\n\nA much more convient way is to be able to run a portion of your coroutine on the\n`MAIN` queue, and some of it on a different queue which is executed on a\nbackground thread.\n\nThe following offloads the loading of the asset to the THREADPOOL queue, and\nthen returns to the MAIN queue when it is done.\n\n```c++\nM.setFunction(\"loadAsset\", [](e_type ctrl) -\u003e task_type\n{\n    PSEUDONIX_PROC_START(ctrl);\n\n    std::filesystem::path p(ARGS[1]);\n\n    // wait and resume on the THREADPOOL taskqueue\n    HANDLE_AWAIT_INT_TERM(co_await ctrl-\u003eawait_yield(\"THREADPOOL\"), ctrl);\n\n    auto asset = loadFromFile(p);\n\n    // return to the main task queue\n    HANDLE_AWAIT_INT_TERM(co_await ctrl-\u003eawait_yield(\"MAIN\"), ctrl);\n            \n    // do what you need with asset\n    co_return 0;\n});\n```\n\nThis, by itself, doesn't do anything and will block forever. You need to\nactually process the THREADPOOL queue. To do this, a special process, `bgrunner`\nhas been created for you to spawn a background thread that processes a queue.\n\n```c++\nPseudoNix::System M;\nPseudoNix::enable_default_shell(M);\nM.setFunction(\"launcher\", PseudoNix::launcher_coro);\n\nM.taskQueueCreate(\"THREADPOOL\");\n\n// Spawn 3 threads to process the THREADPOOL queue\nM.spawnProcess({\"bgrunner\", \"THREADPOOL\"});\nM.spawnProcess({\"bgrunner\", \"THREADPOOL\"});\nM.spawnProcess({\"bgrunner\", \"THREADPOOL\"});\n\n// Spawn the example queueHopper process\nM.spawnProcess({\"queueHopper\", \"THREADPOOL\"});\nM.spawnProcess({\"queueHopper\", \"THREADPOOL\"});\nM.spawnProcess({\"queueHopper\", \"THREADPOOL\"});\nM.spawnProcess({\"queueHopper\", \"THREADPOOL\"});\nM.spawnProcess({\"queueHopper\", \"THREADPOOL\"});\n```\n\nYou can even spawn this from the `shell` command by calling `bgrunner\nTHREADPOOL`.\n\nTry it out using the terminal.\n\n```bash\n# Spawn 3 bgrunners to process the THREADPOOL queue in the background\nspawn 3 bgrunner THREADPOOL\n\n# execute the queueHopper example process 5 times\nspawn 5 queueHopper THREADPOOL\n\n```\n\n## FileSystem\n\nPseudoNix provides a virtual filesystem implementation. Files/Folders can exist\ncompletely in memory, or be mounted from the host.\n\n```c++\n\nPseudoNix::System M;\n\n// create a folder in the virtual file system\nM.mkdir(\"/bin\");\n\n// create an empty file\nM.mkfile(\"/bin/hello.sh\");\n\n// List all files/folders\nfor(auto u : M.list_dir(/mnt))\n{\n    COUT \u003c\u003c std::format(\"{}\\n\", u.generic_string());\n}\n```\n\nSome common filesystem utilities are also provided for the shell:\n\n * ls\n * cd\n * mount/umount\n * pwd\n * mkdir\n * touch\n * cp - single file only. No directories, no globbing\n * mv - single file only. No directories, no globbing\n\n### Mounting Host Directories\n\nYou can mount a host directory inside the virtual file system using the following:\n\n```c++\n\n#include \u003cPseudoNix/System.h\u003e\n#include \u003cPseudoNix/Shell.h\u003e\n#include \u003cPseudoNix/HostMount.h\u003e\n#include \u003cPseudoNix/ArchiveMount.h\u003e // requires libarchive\n\nint main()\n{\n    PseudoNix::System M;\n\n    PseudoNix::enable_default_shell(M); // gives you the default shell process\n    PseudoNix::enable_host_mount(M);    // lets you mount host file systems    \n    PseudoNix::enable_archive_mount(M); // lets you mount tar/tar.gz files\n\n    // mount the user's home folderfolder\n    M.mkdir(\"/host\");\n    sys.mount\u003cPseudoNix::HostMount\u003e(\"/host\", \"/home/user\");\n\n    // mount an uncompressed tar file\n    M.mkdir(\"/tar\");\n    M.mount\u003cPseudoNix::ArchiveMount\u003e(\"/tar\", \"/path/to/archive.tar\");\n    \n    // mount a compressed tar file\n    M.mkdir(\"/tar.gz\");\n    M.mount\u003cPseudoNix::ArchiveMount\u003e(\"/tar\", \"/path/to/archive.tar.gz\");\n\n    // mount a tar file that is in embedded memory\n    M.mkdir(\"/tar_embedded\");\n    M.mount\u003cPseudoNix::ArchiveMount\u003e(\"/tar_embedded\", data_ptr, data_size);\n}\n\n```\n\n### Accessing File Content\n\nNow that you have either created virtual files or mounted host directories. You can \ncan access the file data in a number of ways.\n\n```c++\n    // Simple append a string to an already created file\n    M.mkfile(\"/path/to/file.txt\");\n    M.fs(\"/path/to/file.txt\") \u003c\u003c \"Hello world\";\n\n    // write the data to a string\n    std::string read;\n    M.fs(\"/path/to/file.txt\") \u003e\u003e read;\n\n    // get an std::istream to read directly from\n    // the data\n    auto in = M.openRead(\"/path/to/file.txt\");\n    while(in.eof())\n    {\n        std::string word;\n        in \u003e\u003e word;\n    }\n```\n\nSee the [Filesystem Unit Test](/test/unit-FileSystem2.cpp) for more usage.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgavinnl%2Fpseudonix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgavinnl%2Fpseudonix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgavinnl%2Fpseudonix/lists"}