{"id":21025631,"url":"https://github.com/robertcorponoi/godot-redux-rust-source","last_synced_at":"2025-10-13T08:42:55.218Z","repository":{"id":96308948,"uuid":"344667088","full_name":"robertcorponoi/godot-redux-rust-source","owner":"robertcorponoi","description":"A nativescript implementation of Redux for Godot","archived":false,"fork":false,"pushed_at":"2021-03-05T02:38:22.000Z","size":8,"stargazers_count":3,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-09T01:59:54.570Z","etag":null,"topics":["godot","godot-engine","nativescript","redux","state","state-management"],"latest_commit_sha":null,"homepage":"","language":"Rust","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/robertcorponoi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2021-03-05T02:09:21.000Z","updated_at":"2024-05-30T16:46:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"76646f9e-a587-480a-8098-4d34a3e17857","html_url":"https://github.com/robertcorponoi/godot-redux-rust-source","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/robertcorponoi/godot-redux-rust-source","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertcorponoi%2Fgodot-redux-rust-source","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertcorponoi%2Fgodot-redux-rust-source/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertcorponoi%2Fgodot-redux-rust-source/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertcorponoi%2Fgodot-redux-rust-source/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robertcorponoi","download_url":"https://codeload.github.com/robertcorponoi/godot-redux-rust-source/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertcorponoi%2Fgodot-redux-rust-source/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279014327,"owners_count":26085492,"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","status":"online","status_checked_at":"2025-10-13T02:00:06.723Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["godot","godot-engine","nativescript","redux","state","state-management"],"created_at":"2024-11-19T11:34:56.878Z","updated_at":"2025-10-13T08:42:55.202Z","avatar_url":"https://github.com/robertcorponoi.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg width=\"222\" height=\"208\" src=\"https://raw.githubusercontent.com/robertcorponoi/graphics/master/godot-redux/logo/godot-redux-rust-logo.png\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eGodot Redux Rust\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003eA nativescript implementation of Redux.\u003cp\u003e\n\n**Note:** This is the source code for the nativescript version of [Godot Redux](https://github.com:robertcorponoi/godot-redux). Although this is in Rust, this is still aimed at people who want to use Redux in gdscript. If you're looking for a pure Rust solution to Redux, I recommend checking out [redux-rs](https://github.com/redux-rs/redux-rs). The source code for this can be found at [godot-redux-rust-source](https://github.com:robertcorponoi/godot-redux-rust-source).\n\nRedux is a way to manage and update your application's state using events\ncalled actions. The Redux store serves as a centralized place for data that\ncan be used across your entire application.\n\n**Table of Contents**\n\n- [Concepts](#concepts)\n    - [Initial State](#initial-state)\n    - [Actions](#actions)\n    - [Reducers](#reducers)\n    - [Store](#store)\n    - [Dispatching](#dispatching)\n    - [Middleware](#middleware)\n- [Full Example](#full-example)\n- [How To Use the Store In Other Scripts](#how-to-use-the-store-in-other-scripts)\n- [API](#api)\n    - [new](#new)\n    - [set_state_and_reducer](#set_state_and_reducer)\n    - [state](#state)\n    - [dispatch](#dispatch)\n    - [subscribe](#subscribe)\n    - [add_middleware](#add_middleware)\n- [License](#license)\n\n## Concepts\n\nThe data in Redux is immutable. While this would be great to be able to\nenforce in gdscript, it is not currently possible and so it is up to you to\nfollow the rules and best practices of how to modify data in your actions as\nyou'll see further down in the Reducers section.\n\n### Initial State\n\nThe state in redux is stored in an object called the store. While this can be\nanything, most of the time you'll be using a dictionary of values. Let's take\na look at a simple state that has a counter variable:\n\n```gd\nconst state = {\n    \"counter\": 0\n}\n```\n\n### Actions\n\nActions are the only way to change the state of a Redux application and in\ngdscript they are represented by an enum. Let's expand the simple counter\nexample from above and create actions to increment and decrement the counter.\n\n```gd\nenum Action {\n    INCREMENT,\n    DECREMENT,\n}\n```\n\n### Reducers\n\nTo actually change the values in the state, we need to create reducers. A\nreducer is a function that takes the current state and an action and decides\nhow to update the state if necessary. The example below shows how to create a\nreducer for incrementing and decrementing the counter:\n\n```gd\nconst state = {\n    \"counter\": 0,\n}\n\nenum Action {\n    INCREMENT,\n    DECREMENT,\n}\n\nfunc reducer(state, action):\n    match action:\n        Action.INCREMENT:\n            return {\n                \"counter\": state.counter + 1,\n            }\n        Action.DECREMENT:\n            return {\n                \"counter\": state.counter - 1,\n            }\n```\n\nA couple things to keep in mind here. First we have to again stress that the\nstate is immutable. This means that you MUST return a new state from your\nreducer. Second, while you have to return a new state, you can use values from\nyour old state data to create the new state.\n\n### Store\n\nSo far we've discussed the core components and can put them together into the\nstore. The store must be instanced with the initial state, the current\ninstance, and the name of the reducer function. The current instance and the\nreducer function name must be provided so that we can keep a reference to it.\nA full example with the store can look like:\n\n```gd\nconst state = {\n    \"counter\": 0,\n}\n\nenum Action {\n    INCREMENT,\n    DECREMENT,\n}\n\nfunc reducer(state, action):\n    match action:\n        Action.INCREMENT:\n            return {\n                \"counter\": state.counter + 1,\n            }\n        Action.DECREMENT:\n            return {\n                \"counter\": state.counter - 1,\n            }\n\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n```\n\n### Dispatching\n\nTo actually update the store you have to use the `dispatch` method with the\naction you want to run. This will cause the store to run the reducer function\nand save the new state value.\n\n```gd\nstore.dispatch(Action.INCREMENT)\n```\n\nThis will make the store run the reducer for `INCREMENT` and make the counter\ngo from 0 to 1.\n\n### Subscriptions\n\nTo listen for changes to the state you can use a subscription. The\nsubscription will be called any time an action is dispatched, and some of the\nstate might have changed. To create a subscriber, you have to pass the\ninstance and the name of the function that should be run when the state is\nchanged like so:\n\n```gd\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n\n    store.subscribe(self, 'display_counter')\n\nfunc display_counter(state):\n    print(state.counter)\n```\n\nNow whenever the state is changed with dispatch, the `display_counter`\nfunction will be run and the counter will be printed to the console.\n\n### Middleware\n\nMiddleware is used to customize the dispatch function. This is done by\nproviding you a point between dispatching an action and it reaching the\nreducer. Each piece of middleware added will use the action returned by the\nprevious middleware and if nothing is returned then the middleware chain stops\nbeing processed.\n\nBelow is an example of a middleware function that will take the current action\nand reverse it:\n\n```gd\nfunc reverse_middleware(state, action):\n    match action {\n        Action.INCREMENT:\n            return Action.DECREMENT\n        Action.DECREMENT:\n            return Action.INCREMENT\n\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n    \n    store.add_middleware(self, 'reverse_middleware')\n\n    This will actually run the `DECREMENT` action because of our middleware.\n    store.dispatch(Action.INCREMENT)\n```\n\n## Full Example\n\nIn this section we'll go through a full example of how you can add Godot Redux to your project and use it in various ways.\n\n1. If you're in the source repo you'll need to head over to the [godot-redux-rust](https://github.com:robertcorponoi/godot-redux-rust) repo that contains the compiled code. Once there, copy the `bin` folder into the root (`res://`) directory of your Godot project. If you already have a `bin` folder in your Godot project then just put the contents of the bin folder into the existing bin folder.\n\n2. Create a new gdscript to create your store in.\n\n3. In this script, the first thing we need to do is load the `store.gd` script like so:\n\n```gd\nvar Store = load(\"res://bin/godot_redux/godot_redux.gdns\")\n```\n\n4. Next, we need to create our initial store. Remember that the store is a Dictionary of values and for our simple counter example it will look something like:\n\n```gd\nconst state = {\n    \"counter\": 0\n}\n```\n\n5. Next we need to define our actions. Actions are defined as an enum like so:\n\n```gd\nenum Action {\n    INCREMENT,\n    DECREMENT,\n}\n```\n\n6. Now we have to create the reducer function which will dictate what happens when the `INCREMENT` or `DECREMENT` action is dispatched.\n\n```gd\nfunc reducer(state, action):\n    match action:\n        Action.INCREMENT:\n            return {\n                \"counter\": state.counter + 1,\n            }\n        Action.DECREMENT:\n            return {\n                \"counter\": state.counter - 1,\n            }\n```\n\nSo the reducer function will always take the state and action being dispatched as its arguments. We use a match statement so taht when `INCREMENT` is used, the counter will increase by 1 and when `DECREMENT` is used, the counter will decrease by 1. Two other things to note here. One, notice that we're returning a new copy of the state in each match arm and two, we can use the previous value of the state to create the new one.\n\n7. Let's put this all together and create the `Store` instance. To create a new store instance, we need to pass in our initial state, the class instance that has the reducer function, and the name of the reducer function. Note that this part is different than the gdscript implementation of godot-redux due to limitations with Godot Rust. With the nativescript version, we have to pass the initial state and reducers in the `set_state_and_reducer` method like so:\n\n```gd\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n```\n\nSince our reducer function is just named 'reducer' and it's on the current class instnace, we can just pass `self` and 'reducer` as the last two arguments.\n\n8. Now we're ready to try dispatching and see how it affects our state. Let's try dispatching the `INCREMENT` action twice and then the `DECREMENT` action once like so:\n\n```gd\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n\n    store.dispatch(Action.INCREMENT)\n    store.dispatch(Action.INCREMENT)\n    store.dispatch(Action.DECREMENT)\n```\n\nIf everything worked correctly, this should put the counter at 1, so let's see:\n\n```gd\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n\n    store.dispatch(Action.INCREMENT)\n    store.dispatch(Action.INCREMENT)\n    store.dispatch(Action.DECREMENT)\n\n    print(store.state()) # { \"counter\": 1 }\n```\n\nThe above is a basic example of how to set up and use the store. We didn't go over all of the available methods though so now we're going to do a couple examples of `subscribe` and `add_middleware`.\n\n`subscribe` - The `subscribe` method is used to add a listener to state changes. This means that whenever the state might change, the method that was subscribed will be called and it will be passed the current state as an argument. Below is an example of how you could create a subscriber that would print the counter state whenever it changes:\n\n```gd\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n\n    store.subscribe(self, 'counter_printer')\n\nfunc counter_printer(state):\n    print(state.counter)\n```\n\nNote that `subscribe` is similar to how the store was initialized in that you have to pass the class instance that contains the subscribe function and then the name of the function.\n\n`add_middleware` - The `add_middleware` method is used to add middleware that can alter the action of a dispatch before it reaches the reducer. The middleware will be passed the current state and the action that was used as arguments. As a simple example, we'll go through creating a middleware that will take the action and return the opposite action:\n\n```gd\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n\n    store.add_middleware(self, 'reverse_action')\n\nfunc reverse_action(state, action):\n    if (action == Action.INCREMENT):\n        return Action.DECREMENT\n    elif (action == Action.DECREMENT):\n        return Action.INCREMENT\n```\n\nNotice that the `add_middleware` is similar to `subscribe` in that it takes the class instance that contains the middleware function and then the name of the middleware function as arguments.\n\nWe can test to see if this works by dispatching the same actions we did earlier like so:\n\n```gd\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n\n    store.add_middleware(self, 'reverse_action')\n\n    store.dispatch(Action.INCREMENT)\n    store.dispatch(Action.INCREMENT)\n    store.dispatch(Action.DECREMENT)\n\n    print(store.state()) # { \"counter\": -1 }\n\nfunc reverse_action(state, action):\n    if (action == Action.INCREMENT):\n        return Action.DECREMENT\n    elif (action == Action.DECREMENT):\n        return Action.INCREMENT\n```\n\nInstead of returning 1 as the value of the counter it should now return -1.\n\n## How To Use the Store In Other Scripts\n\nWhere you set up the store might not be where you always need to use it. In this case, it might be better to look at autoloading the script where you created your store.\n\nFor example let's assume that you want to create a `save.gd` script where you'll set up the store. We'll just use the basic counter example from above:\n\n`save.gd`\n\n```gd\nvar store\n\nconst state = {\n    \"counter\": 0,\n}\n\nenum Action {\n    INCREMENT,\n    DECREMENT,\n}\n\nfunc reducer(state, action):\n    match action:\n        Action.INCREMENT:\n            return {\n                \"counter\": state.counter + 1,\n            }\n        Action.DECREMENT:\n            return {\n                \"counter\": state.counter - 1,\n            }\n\nfunc _ready():\n    store = Store.new(state, self, 'reducer')\n```\n\nThe only difference here is that we declare `store` at the top level so that we can reference it from outside of this script.\n\nNow you can go to `Project -\u003e AutoLoad` and select your `save.gd` script with a Node name that represents the name of the global variable you'll use to access this script (for this example we'll use `Save`).\n\nLastly, you can create a new script and use your store instance like so:\n\n```gd\nextends Node\n\nfunc _ready():\n    Save.store.dispatch(Save.Action.INCREMENT)\n    Save.store.dispatch(Save.Action.DECREMENT)\n    \n    print(Save.store.state()) # { \"counter\": 0 }\n```\n\nAs shown in the example above you can now use your store anywhere by referencing the global `Save` variable.\n\n## API\n\n### new\n\nCreates a new Redux store.\n\n**Example:**\n\n```gd\nconst state = {\n    \"counter\": 0,\n}\n\nenum Action {\n    INCREMENT,\n    DECREMENT,\n}\n\nfunc reducer(state, action):\n    match action:\n        Action.INCREMENT:\n            return {\n                \"counter\": state.counter + 1,\n            }\n        Action.DECREMENT:\n            return {\n                \"counter\": state.counter - 1,\n            }\n\nfunc _ready():\n    var store = Store.new()\n```\n\n### set_state_and_reducer\n\nSets the initial state and reducer for the store. Normally this is provided on initialization but due to a limitation of Godot Rust, we have to pass these values through this method.\n\n| param               | type       | description                                            |\n|---------------------|------------|--------------------------------------------------------|\n| state               | Dictionary | The initial state of the application.                  |\n| reducer_fn_instance | Object     | The class instance that contains the reducer function. |\n| reducer_fn_name     | String     | The name of the reducer function.                      |\n\n**Example:**\n\n```gd\nconst state = {\n    \"counter\": 0,\n}\n\nenum Action {\n    INCREMENT,\n    DECREMENT,\n}\n\nfunc reducer(state, action):\n    match action:\n        Action.INCREMENT:\n            return {\n                \"counter\": state.counter + 1,\n            }\n        Action.DECREMENT:\n            return {\n                \"counter\": state.counter - 1,\n            }\n\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n```\n\n### state\n\nReturns the current state.\n\n**Example:**\n\n```gd\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n\n    var state = store.state()\n```\n\n### dispatch\n\nRuns the reducer function for the specified action.\n\n| param  | type | description                        |\n|--------|------|------------------------------------|\n| action | Enum | The action to pass to the reducer. |\n\n**Example:**\n\n```gd\nconst state = {\n    \"counter\": 0,\n}\n\nenum Action {\n    INCREMENT,\n    DECREMENT,\n}\n\nfunc reducer(state, action):\n    match action:\n        Action.INCREMENT:\n            return {\n                \"counter\": state.counter + 1,\n            }\n        Action.DECREMENT:\n            return {\n                \"counter\": state.counter - 1,\n            }\n\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n\n    store.dispatch(Action.INCREMENT)\n```\n\n### subscribe\n\nCreates a subscriber that gets called whenever the state is changed. The callback function provided will be passed the current state as an argument.\n\n| param                | type   | description                                                        |\n|----------------------|--------|--------------------------------------------------------------------|\n| callback_fn_instance | Object | The class instance that contains the subscriber callback function. |\n| callback_fn_name     | String | The name of the callback function.                                 |\n\n**Example:**\n\n```gd\nconst state = {\n    \"counter\": 0,\n}\n\nenum Action {\n    INCREMENT,\n    DECREMENT,\n}\n\nfunc reducer(state, action):\n    match action:\n        Action.INCREMENT:\n            return {\n                \"counter\": state.counter + 1,\n            }\n        Action.DECREMENT:\n            return {\n                \"counter\": state.counter - 1,\n            }\n\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n\n    store.subscribe(self, 'counter_printer')\n\nfunc counter_printer(state):\n    print(state.counter)\n```\n\n### add_middleware\n\nAdds a middleware function to intercept dispatches before they reach the reducer. Middleware can be used to change the action to run.\n\n| param                  | type   | description                                               |\n|------------------------|--------|-----------------------------------------------------------|\n| middleware_fn_instance | Object | The class instance that contains the middleware function. |\n| middleware_fn_name     | String | The name of the middleware function.                      |\n\n**Example:**\n\n```gd\nfunc _ready():\n    var store = Store.new()\n    store.set_state_and_reducer(state, self, 'reducer')\n    \n    store.add_middleware(self, 'reverse_action')\n\n    store.dispatch(Action.INCREMENT)\n    store.dispatch(Action.INCREMENT)\n    store.dispatch(Action.DECREMENT)\n\n    print(store.state()) # { \"counter\": -1 }\n\nfunc reverse_action(state, action):\n    if (action == Action.INCREMENT):\n        return Action.DECREMENT\n    elif (action == Action.DECREMENT):\n        return Action.INCREMENT\n```\n\n## License\n\n[MIT](./LICENSE)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobertcorponoi%2Fgodot-redux-rust-source","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobertcorponoi%2Fgodot-redux-rust-source","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobertcorponoi%2Fgodot-redux-rust-source/lists"}