{"id":18497580,"url":"https://github.com/csb6/bthreads","last_synced_at":"2025-05-14T05:14:20.811Z","repository":{"id":115656143,"uuid":"200157869","full_name":"csb6/bthreads","owner":"csb6","description":"An implementation of behavioral programming in Python","archived":false,"fork":false,"pushed_at":"2019-08-26T06:36:56.000Z","size":28,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-25T17:42:12.909Z","etag":null,"topics":["behavioral-programming"],"latest_commit_sha":null,"homepage":null,"language":"Python","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/csb6.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2019-08-02T03:24:20.000Z","updated_at":"2020-12-22T00:08:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"8d276a0c-e811-4592-a465-367580c0788a","html_url":"https://github.com/csb6/bthreads","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csb6%2Fbthreads","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csb6%2Fbthreads/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csb6%2Fbthreads/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/csb6%2Fbthreads/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/csb6","download_url":"https://codeload.github.com/csb6/bthreads/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239213599,"owners_count":19601006,"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":["behavioral-programming"],"created_at":"2024-11-06T13:35:00.930Z","updated_at":"2025-02-17T00:20:01.365Z","avatar_url":"https://github.com/csb6.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bthreads\n\nThis is an implementation of a Behavioral Programming (BP) system in Python.\nUnlike other programming paradigms, BP focuses on incrementally adding \"rules\"\nor behaviors that should occur when events that match certain criteria happen.\nThe program is made of of b-threads, which are blocks of code that can\nrequest an event to happen, block a requested event from happening, or\nwait for an event to occur. After any one of these actions, the b-thread\nshould yield control back to the scheduler, which determines which events,\nif any, to trigger. When an event triggers, all of the objects that requested\nit or decided to wait for it get notified and regain control, allowing them\nto request, wait for, or block more events.\n\nWhile many BP implementations are fully parallel, this implementation is\nsingle-threaded, making use of Python generators as a sort of coroutine.\n\n## Installation\n\nJust clone this repository. The only file you need is bthreads.py.\nAdd that to your interpreter's path, and import it like any other\nPython module. Feel free to look at/run the example programs\nto see the features of the library. Be aware that this is an\nexperimental project and so subject to unstable changes/bugs.\n\nThe module is tested with 3.7.0, but should work with any Python 3\nversion.\n\n## Usage\n\nUsage is fairly simple. Firstly, you need to create a BProgram object, which\nkeeps track of the b-threads and schedules/decides which event occurs:\n\n```\nfrom bthreads import *\n\nbp = BProgram()\n```\n\nCreating bthreads is as easy as creating normal Python\ngenerators. Just include your BProgram instance as an argument\nin the decorator in order to add a new b-thread to it:\n\n```\n@bthread(bp)\ndef sayWorld(thread):\n    thread.sync(request=BEvent(\"World!\"))\n    yield\n\nbp.run()\n```\n\nOutput:\n\n```\nEvent occurred: World!\n```\n\n`thread` is a BThread object, representing the state of the b-thread\nand also including a handy method, `sync()`, which allows the function\nto request or wait for an event of set of events, with an option to\nblock an event/set of events until that happens (not used here).\n`sayWorld` requests an event, yields back control, and since no other\nb-thread blocked the request, the event occurs. Here's a slightly more\ncomplex example:\n\n```\n@bthread(bp)\ndef sayWorld(thread):\n    thread.sync(request=BEvent(\"World!\"))\n    yield\n\n@bthread(bp)\ndef sayHello(thread):\n    thread.sync(request=BEvent(\"Hello,\"), block=BEvent(\"World!\"))\n    yield\n\nbp.run()\n```\n\nOutput:\n\n```\nEvent occurred: Hello,\nEvent occurred: World!\n```\n\nThe sayHello b-thread blocks any event named\n\"World!\" from happening until its requested event, \"Hello,\", occurs.\nSince no other b-thread is blocking \"Hello,\", it occurs, ending the\ntemporary block and allowing BEvent(\"World!\") to trigger.\n\nHere's an example involving wait:\n\n```\n@bthread(bp)\ndef sayWorld(thread):\n    thread.sync(request=BEvent(\"World!\"))\n    yield\n\n@bthread(bp)\ndef sayHello(thread):\n    thread.sync(request=BEvent(\"Hello,\"), block=BEvent(\"World!\"))\n    yield\n\n@bthread(bp)\ndef sayGodForsaken(thread):\n    thread.sync(wait=BEvent(\"Hello,\"))\n    yield\n    thread.sync(request=BEvent(\"God-forsaken\"), block=BEvent(\"World!\"))\n    yield\n\nbp.run()\n```\n\nOutput:\n\n```\nEvent occurred: Hello,\nEvent occurred: God-forsaken\nEvent occurred: World!\n```\n\nThe third b-thread waits until the \"Hello,\" event occurs.\nWhen it occurs, it is given back control, causing it to\nrequest a new event, \"God-forsaken\" and block \"World!\"\nuntil \"God-forsaken\" occurs. Since no other b-thread is\nblocking it, \"God-forsaken\" occurs, thus lifting the\nblock on \"World!\", which then occurs.\n\nThis is a really powerful way of programming, even though\nit seems kind of verbose for such a simple task as printing\nout one's dissatisfaction with Planet Earth. But it becomes more\nuseful when you think about adding to existing programs.\n\nSay you just had the first snippet running, containing the \"Hello,\"\nb-thread, but you didn't have access to the source code.\nReplicating the second snippet would be pretty easy without\nknowing the old code; you can see from the event log\nthat \"Hello,\" occurs and then the program ends. You can\nsimply add another b-thread that requests a new event, blocking\n\"Hello,\" until it happens, in order to change the behavior.\n\nGoing from the second to the third snippet is similar. Just\nlook at the event log, see what events you need to wait\nfor in order to insert your new event, and request it while\nblocking the next event, \"World!\"\n\nBy building new \"rules\" or behaviors like this, one on top the other,\nwithout necessarily understanding all the prior code, you can create\nimmensely complex behavior with simple rules that basically say:\n\"Do this thing after this event happens, and block this other\nevent until you do it.\" or \"Request this thing to happen.\"\n\n## Advanced Usage\n\nWhile the above examples work well for matching a single\nevent, what if you wanted to request/wait for a group of\nevents? That's where `BEventSet` comes in handy. For\nexample, consider code that reacts to a user entering input:\n\n```\nfrom bthreads import *\nbp = BProgram()\n\n@bthread(bp)\ndef getInput(thread):\n    thread.sync(request=BEvent(\"getInput\"))\n    yield\n    answer = input(\"Would you like to view a contrived example? [y/n]\")\n    if answer.lower() == \"y\":\n       thread.sync(request=BEvent(\"contrivedExample\"))\n    elif answer.lower() == \"n\":\n       thread.sync(request=BEvent(\"noExample\"))\n    yield\n\n@bthread(bp)\ndef rejectAnswer(thread):\n    thread.sync(wait=BEventSet(\"answers\", [BEvent(\"contrivedExample\"), BEvent(\"noExample\")]))\n    yield\n    thread.sync(request=BEvent(\"Nevermind\"), block=BEvent(\"getInput\"))\n    yield\n\nbp.run()\n```\n\nOutput:\n\n```\nEvent Occurred: getInput\nWould you like to view a contrived example? [y/n]y\nEvent Occurred: contrivedExample\nEvent Occurred: Nevermind\n```\n\nThe first b-thread, `getInput`, requests different events based on the user's\nchoice. `rejectAnswer` waits for either event to occur, with all the events it\nwants to match with in a list within a `BEventSet`. Note that requests can't\nuse BEventSets; you can only request specific BEvents.\n\nThis format works well if you only have a few events, but what if the events\nyou need to catch have a wide variety of names? Waiting for any event starting\nwith \"movePiece\", for example, in a chess program would require you to list all\npossible squares on the board! Fortunately, this library has an easier way: predicates.\n\nIn addition to accepting lists of BEvent literals, `BEventSet` constructors can\naccept a predicate, which is a function that returns True/False if the given\nevent does/doesn't match the criteria for inclusion in the event set. For example:\n\n```\nfrom bthreads import *\nbp = BProgram()\n\n@bthread(bp)\ndef enterMove(thread):\n    while True:\n        thread.sync(request=BEvent(\"enterMove\"))\n        yield\n        coords = input(\"Enter coords 'x y':\")\n        thread.sync(request=BEvent(\"move\"+coords))\n        yield\n\n@bthread(bp)\ndef switchTurn(thread):\n    isBlueTurn = True\n    while True:\n        thread.sync(wait=BEventSet(\"moves\", lambda e: e.name.startswith(\"move\")))\n        yield\n        if isBlueTurn:\n            thread.sync(request=BEvent(\"BlueTurn\"))\n        else:\n            thread.sync(request=BEvent(\"RedTurn\"))\n        isBlueTurn = not isBlueTurn\n        yield\n\n@bthread(bp)\ndef endTurn(thread):\n    while True:\n        thread.sync(wait=BEventSet(\"moves\", lambda e: e.name.startswith(\"move\")))\n        yield\n        thread.sync(wait=BEventSet(\"turnEnded?\", lambda e: e.name.endswith(\"Turn\")), block=BEvent(\"enterMove\"))\n        yield\n\nbp.run()\n```\n\nThis program allows the user to enter in moves as coordinates, then\nalternates between red and blue turns. `switchTurn` waits until any event\nwith a name starting with \"move\" to occur, then requests \"BlueTurn\" or\n\"RedTurn\"; after each loop iteration, a flag is flipped, ensuring that\nevery time input is entered, the turn switches.\n\nThe third bthread, `endTurn`, starting blocking \"enterMove\", the event\nwhich will trigger another user input, after a move occurs (in the same way\nas `switchTurn` does). As soon as `switchTurn` triggers a Blue or RedTurn event,\nthe block lifts, and \"enterMove\" can once again happen. This behavior continues\nin an infinite loop: enter move, turn is blue/red, turn ends, repeat.\n\nWhile this example used a short lambda to determine which events belonged in the\nevent set, any Python function returning True/False would work the same way.\nJust pass in the function name as the second argument in `BEventSet()`\n\nI hope you find this project useful and interesting!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcsb6%2Fbthreads","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcsb6%2Fbthreads","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcsb6%2Fbthreads/lists"}