{"id":14961787,"url":"https://github.com/marcello505/godot-ggrs-wrapper","last_synced_at":"2025-09-08T06:34:42.622Z","repository":{"id":44140493,"uuid":"399808506","full_name":"marcello505/godot-ggrs-wrapper","owner":"marcello505","description":"A Rust GDNative project to provide GGRS to the Godot Engine","archived":false,"fork":false,"pushed_at":"2022-03-19T15:26:55.000Z","size":265,"stargazers_count":21,"open_issues_count":5,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-31T04:26:02.746Z","etag":null,"topics":["gamedev","gdnative","ggrs","godot","godotengine","rollback","rollback-netcode","rust"],"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/marcello505.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}},"created_at":"2021-08-25T12:22:37.000Z","updated_at":"2023-12-15T07:59:13.000Z","dependencies_parsed_at":"2022-09-15T14:40:10.458Z","dependency_job_id":null,"html_url":"https://github.com/marcello505/godot-ggrs-wrapper","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcello505%2Fgodot-ggrs-wrapper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcello505%2Fgodot-ggrs-wrapper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcello505%2Fgodot-ggrs-wrapper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcello505%2Fgodot-ggrs-wrapper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcello505","download_url":"https://codeload.github.com/marcello505/godot-ggrs-wrapper/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238039755,"owners_count":19406395,"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":["gamedev","gdnative","ggrs","godot","godotengine","rollback","rollback-netcode","rust"],"created_at":"2024-09-24T13:27:37.007Z","updated_at":"2025-02-10T00:32:31.027Z","avatar_url":"https://github.com/marcello505.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# godot-ggrs-wrapper\n\nThe purpose of this repo is to create a wrapper for the [ggrs](https://github.com/gschup/ggrs) Rust crate so that this can be used inside the Godot Game Engine. To accomplish this the [godot-rust](https://github.com/godot-rust/godot-rust) GDNative bindings are used. Although the project currently implements all of the GGRS features, **i can't guarantee** that it's ready for use in production.\n\n## Notes\n\n- It's recommended that you set `reloadable` to `false` inside the `GDNativeLibrary` Godot resource.\n- Tested on Godot version 3.3.2, Godot 4.0 will have vastly expanded GDNative capabilities so when that comes out it can be assumed that this project will break.\n- Inputs to GodotGGRS is currently in the format of a unsigned 32-bit integer.\n- States are GodotByteArrays, you convert the godot variant to a ByteArray and give it to GodotGGRS.\n- Compiling requires Clang, see details [here](https://rust-lang.github.io/rust-bindgen/requirements.html).\n\n## Links\n\n- [Documentation](https://docs.rs/godot-ggrs-wrapper/)\n- [Crates.io](https://crates.io/crates/godot-ggrs-wrapper)\n\n## Examples\n\n- [Remote connection between 2 different machines](https://youtu.be/8gwXVMqhNs8)\n- [Example Project](https://github.com/marcello505/godot-ggrs-wrapper/tree/main/example)\n\n## Projects that use godot-ggrs-wrapper\n\nDoes your project use godot-ggrs-wrapper? Let me know and i'll be happy to add a link!\n\n- [Girls With Swords](https://github.com/trian-gles/Girls-with-swords-GGRS)\n\n## Quick start\n\nSince this project uses [godot-rust](https://github.com/godot-rust/godot-rust) GDNative bindings you should be familiar with how to set up a godot-rust GDNative node inside Godot. If you aren't please check out [this page](https://godot-rust.github.io/book/getting-started/hello-world.html) for details. The setup you want to have is a scene with a node that has the GodotGGRSP2PSession class bound to it.\n\n### Starting a session with GodotGGRSP2PSession\n\nTo set up a session you want to have a script that addresses the GGRS node. Just having the node inside your scene doesn't create a session for you. Here i took the root node to make a script that makes a session on the **\\_ready()** function\n\n```gdscript\nfunc _ready():\n\tvar local_handle: int #This var is later used to identify your local inputs\n\tvar remote_handle: int #This var is later used to identify the remote_players inputs\n\tif(OS.get_cmdline_args()[0] == \"listen\"):\n\t\t$GodotGGRS.create_new_session(7070, 2) # Port 7070, 2 max players, max 8 prediction frames\n\t\tlocal_handle = $GodotGGRS.add_local_player()\n\t\tremote_handle = $GodotGGRS.add_remote_player(\"127.0.0.1:7071\")\n\telif(OS.get_cmdline_args()[0] == \"join\"):\n\t\t$GodotGGRS.create_new_session(7071, 2) # Port 7071, 2 max players, max 8 prediction frames\n\t\tremote_handle = $GodotGGRS.add_remote_player(\"127.0.0.1:7070\")\n\t\tlocal_handle = $GodotGGRS.add_local_player()\n\n\t$GodotGGRS.receive_callback_node(self) # Set the node which will implement the callback methods\n\t$GodotGGRS.set_frame_delay(2, local_handle) # Set personal frame_delay, works only for local_handles.\n\t$GodotGGRS.start_session() #Start listening for a session.\n```\n\nAs you can see we swap the order of adding players depending on who's the \"host\". In reality since it's a peer 2 peer library, there is no true host. However you should have a way to distinguish between player 1 and player 2.\n\n### Advancing frames\n\nNow that we have a session we want to start implementing our loop. Godot's default **\\_process()** and **\\_physics_process()** will serve us nicely here.\n\n```gdscript\nfunc _process(_delta):\n\t$GodotGGRS.poll_remote_clients() # GGRS needs to periodically process UDP requests and such, sticking it in \\_process() works nicely since it's only called on idle.\n\nfunc _physics_process(_delta):\n\tif($GodotGGRS.is_running()): # This will return true when all players and spectators have joined and have been synched.\n\t\t$GodotGGRS.advance_frame(local_handle, raw_input_to_int(\"con1\")) # raw_input_to_int is a method that parses InputActions that start with \"con1\" into a integer.\n\nfunc raw_input_to_int(prefix: String)-\u003eint:\n\t# This method is how i parse InputActions into an int, but as long as it's an int it doesn't matter how it's parsed.\n\tvar result := 0;\n\tif(Input.is_action_pressed(prefix + \"_left\")): #The action it checks here would be \"con1_left\" if the prefix is set to \"con1\"\n\t\tresult |= 1\n\tif(Input.is_action_pressed(prefix + \"_right\")):\n\t\tresult |= 2\n\tif(Input.is_action_pressed(prefix + \"_up\")):\n\t\tresult |= 4\n\tif(Input.is_action_pressed(prefix + \"_down\")):\n\t\tresult |= 8\n\treturn result;\n```\n\nCalling advance_frame will tell GGRS that you are ready to go to the next frame using the input you've given as a parameter. GGRS will do it's thing and callback to Godot once it's ready to continue.\n\n### Handling GGRS callbacks\n\nSo how to handle GGRS callbacks is alot more subjective than the steps before and will vary greatly on how your game is built. The only thing required is that you implement the callback functions, but the logic inside can be pretty much anything to fit to your game. Here's how i implemented the callback methods.\n\n```gdscript\nfunc ggrs_advance_frame(inputs: Array):\n\t# inputs is an array of input data indexed by handle.\n\t# input_data itself is also an array with the following: [frame: int, size: int, inputs: int]\n\t# frame can be used as a sanity check, size is used internally to properly slice the buffer of bytes and inputs is the int we created in our previous step.\n\tvar net1_inputs := 0;\n\tvar net2_inputs := 0;\n\tif(local_handle \u003c remote_handle):\n\t\tnet1_inputs = inputs[local_handle][2]\n\t\tnet2_inputs = inputs[remote_handle][2]\n\telse:\n\t\tnet1_inputs = inputs[remote_handle][2]\n\t\tnet2_inputs = inputs[local_handle][2]\n\tint_to_raw_input(\"net1\", net1_inputs) # Player objects check for InputActions that aren't bound to any controller.\n\tint_to_raw_input(\"net2\", net2_inputs) # Player objects check for InputActions that aren't bound to any controller.\n\t_handle_player_frames()\n\nfunc ggrs_load_game_state(frame: int, buffer: PoolByteArray, checksum: int):\n\tvar state : Dictionary = bytes2var(buffer);\n\tP1.load_state(state.get(\"P1\", {}))\n\tP2.load_state(state.get(\"P2\", {}))\n\nfunc ggrs_save_game_state(frame: int)-\u003ePoolByteArray: # frame parameter can be used as a sanity check (making sure it matches your internal frame counter).\n\tvar save_state = {}\n\tsave_state[\"P1\"] = _save_P1_state(\"\");\n\tsave_state[\"P2\"] = _save_P2_state(\"\");\n\treturn var2bytes(save_state);\n\nfunc int_to_raw_input(prefix: String, inputs: int):\n\t_set_action(prefix + \"_left\", inputs \u0026 1)\n\t_set_action(prefix + \"_right\", inputs \u0026 2)\n\t_set_action(prefix + \"_up\", inputs \u0026 4)\n\t_set_action(prefix + \"_down\", inputs \u0026 8)\n\nfunc _set_action(action: String, pressed: bool):\n\tif(pressed):\n\t\tInput.action_press(action)\n\telse:\n\t\tInput.action_release(action)\n\n```\n\n## Handling Rust Panics\n\nCreate a godot script containing the following:\n\n```gdscript\nextends Node\n\nfunc rust_panic_hook(error_msg: String) -\u003e void:\n    assert(false, error_msg)\n```\n\nMake the Godot Project autoload this script as a singleton using the following name: \"RustPanicHook\". Now all Rust panics should always be catched properly. This solution is based off of the [Godot-Rust - Rust Panic Hook Recipe](https://godot-rust.github.io/book/recipes/rust_panic_handler.html).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcello505%2Fgodot-ggrs-wrapper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcello505%2Fgodot-ggrs-wrapper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcello505%2Fgodot-ggrs-wrapper/lists"}