Skip to content

Instantly share code, notes, and snippets.

@HeatXD
Created January 14, 2023 15:30
Show Gist options
  • Save HeatXD/d28462034351a50f8412478c5ff40f60 to your computer and use it in GitHub Desktop.
Save HeatXD/d28462034351a50f8412478c5ff40f60 to your computer and use it in GitHub Desktop.
godot 4 -> ggrs
use ggrs::{Config, GGRSError, P2PSession, PlayerType, SessionBuilder, UdpNonBlockingSocket, GameStateCell};
use godot::prelude::*;
use std::{
collections::{HashMap, VecDeque},
net::SocketAddr,
};
pub struct GGRSConfig;
impl Config for GGRSConfig {
type Input = i32;
type State = Variant;
type Address = SocketAddr;
}
#[derive(GodotClass)]
#[class(base=RefCounted)]
pub struct GodotGGRSInput{
/*
Input Status
1000 = Confirmed Input
2000 = Predicted Input
3000 = Disconnected Input
*/
status: i32,
input: i32,
#[base]
base: Base<RefCounted>,
}
#[godot_api]
impl GodotGGRSInput {
#[func]
fn status(&mut self) -> i32 {
self.status
}
#[func]
fn input(&mut self) -> i32 {
self.input
}
fn set(&mut self, input: i32, status: i32) {
self.input = input;
self.status = status;
}
}
#[derive(GodotClass)]
#[class(base=RefCounted)]
pub struct GodotGGRSRequest {
/*
Request Codes
1000 = GGRSRequest::SaveGameState
2000 = GGRSRequest::LoadGameState
3000 = GGRSRequest::AdvanceFrame
*/
code: i64,
frame: Option<i32>,
inputs: VecDeque<Gd<GodotGGRSInput>>,
ggrs_cell: Option<GameStateCell<Variant>>,
#[base]
base: Base<RefCounted>,
}
#[godot_api]
impl GodotGGRSRequest {
#[func]
fn frame(&mut self) -> i32 {
if let Some(frame) = self.frame {
return frame;
}
return -1;
}
#[func]
fn code(&mut self) -> i64 {
self.code
}
#[func]
fn state_save(&mut self, frame: i32, savestate: Variant) {
if let Some(cell) = &self.ggrs_cell{
cell.save(frame, Some(savestate), Some(0));
}
}
#[func]
fn state_load(&mut self) -> Variant {
if let Some(cell) = &self.ggrs_cell{
if let Some(savestate) = cell.load(){
return savestate;
}
}
// if no state is found just give back null
return Variant::default();
}
fn set(&mut self,code: i64, frame: Option<i32>, cell: Option<GameStateCell<Variant>>) {
self.code = code;
self.frame = frame;
self.ggrs_cell = cell;
}
fn add_input(&mut self, input: Gd<GodotGGRSInput>) {
self.inputs.push_back(input);
}
#[func]
fn has_input(&mut self) -> bool {
!self.inputs.is_empty()
}
#[func]
fn get_input(&mut self) -> Gd<GodotGGRSInput> {
if let Some(input) = self.inputs.pop_front() {
return input;
};
// its empty return dummy input
return Gd::<GodotGGRSInput>::new_default();
}
}
#[derive(GodotClass)]
#[class(base=RefCounted)]
pub struct GodotGGRSRequestCollection {
/*
1000 = OK
2000 = SESSION ERROR
3000 = GGRSError::PredictionThreshold
4000 = GGRSError::InvalidRequest
5000 = GGRSError::MismatchedChecksum
6000 = GGRSError::NotSynchronized
7000 = GGRSError::SpectatorTooFarBehind
8000 = Unkown Error
*/
code: i64,
frame: Option<i32>,
info: Option<GodotString>,
requests: VecDeque<Gd<GodotGGRSRequest>>,
#[base]
base: Base<RefCounted>,
}
#[godot_api]
impl GodotGGRSRequestCollection {
fn set(&mut self, code: i64, frame: Option<i32>, info: Option<GodotString>){
self.code = code;
self.frame = frame;
self.info = info;
}
#[func]
fn code(&mut self) -> i64 {
self.code
}
fn add_request(&mut self, event: Gd<GodotGGRSRequest>) {
self.requests.push_back(event);
}
#[func]
fn info(&mut self) -> GodotString {
if let Some(info) = &self.info {
return info.clone();
}
return GodotString::from("");
}
#[func]
fn has_requests(&mut self) -> bool {
!self.requests.is_empty()
}
#[func]
fn get_request(&mut self) -> Gd<GodotGGRSRequest> {
if let Some(req) = self.requests.pop_front() {
return req;
};
// its empty return dummy event
return Gd::<GodotGGRSRequest>::new_default();
}
}
#[derive(GodotClass)]
#[class(base=RefCounted)]
pub struct GodotGGRSEvent {
/*
Event Codes
1000 = GGRSEvent::Synchronizing
2000 = GGRSEvent::Synchronized
3000 = GGRSEvent::NetworkInterrupted
4000 = GGRSEvent::NetworkResumed
5000 = GGRSEvent::WaitRecommendation
6000 = GGRSEvent::Disconnected
*/
code: i64,
addr: Option<GodotString>,
total: Option<u32>,
count: Option<u32>,
disconnect_timeout: Option<u32>,
skip_frames: Option<u32>,
#[base]
base: Base<RefCounted>,
}
#[godot_api]
impl GodotGGRSEvent {
#[func]
fn code(&mut self) -> i64 {
self.code
}
#[func]
fn skip_frames(&mut self) -> u32 {
if let Some(skip) = self.skip_frames {
return skip;
}
return 0;
}
#[func]
fn addr(&mut self) -> GodotString {
if let Some(addr) = &self.addr {
return addr.clone();
}
return GodotString::from("");
}
#[func]
fn total(&mut self) -> u32 {
if let Some(total) = self.total {
return total;
}
return 0;
}
#[func]
fn count(&mut self) -> u32 {
if let Some(count) = self.count {
return count;
}
return 0;
}
#[func]
fn disconnect_timeout(&mut self) -> u32 {
if let Some(timeout) = self.disconnect_timeout {
return timeout;
}
return 0;
}
fn set(
&mut self,
code: i64,
addr: Option<GodotString>,
total: Option<u32>,
count: Option<u32>,
disconnect_timeout: Option<u32>,
skip_frames: Option<u32>,
) {
self.code = code;
self.addr = addr;
self.total = total;
self.count = count;
self.disconnect_timeout = disconnect_timeout;
self.skip_frames = skip_frames;
}
}
#[derive(GodotClass)]
#[class(base=RefCounted)]
pub struct GodotGGRSEventCollection {
events: VecDeque<Gd<GodotGGRSEvent>>,
#[base]
base: Base<RefCounted>,
}
#[godot_api]
impl GodotGGRSEventCollection {
fn add_event(&mut self, event: Gd<GodotGGRSEvent>) {
self.events.push_back(event);
}
#[func]
fn has_events(&mut self) -> bool {
!self.events.is_empty()
}
#[func]
fn get_event(&mut self) -> Gd<GodotGGRSEvent> {
if let Some(ev) = self.events.pop_front() {
return ev;
};
// its empty return dummy event
return Gd::<GodotGGRSEvent>::new_default();
}
}
#[derive(GodotClass)]
#[class(base=Node)]
pub struct GodotGGRS {
show_debug: bool,
players: HashMap<i64, PlayerType<SocketAddr>>,
p2p_session: Option<P2PSession<GGRSConfig>>,
#[base]
base: Base<Node>,
}
#[godot_api]
impl GodotGGRS {
fn has_player(&mut self, player_handle: i64) -> bool {
self.players.contains_key(&player_handle)
}
#[func]
fn show_debug_messages(&mut self, show: bool) {
self.show_debug = show;
}
#[func]
fn add_player_local(&mut self, player_handle: i64) -> bool {
if self.has_player(player_handle) {
return false;
}
self.players.insert(player_handle, PlayerType::Local);
return true;
}
#[func]
fn add_player_remote(&mut self, player_handle: i64, addr: GodotString) -> bool {
if self.has_player(player_handle) {
return false;
}
let result = addr.to_string().parse::<SocketAddr>();
if result.is_err() {
return false;
}
self.players
.insert(player_handle, PlayerType::Remote(result.unwrap()));
return true;
}
#[func]
fn create_p2p_session(
&mut self,
num_players: i32,
fps: i32,
delay: i32,
local_port: u16,
sparse_saving: bool,
) -> bool {
let mut create_sess = || -> Result<(), GGRSError> {
// create session
let mut sess_build = SessionBuilder::<GGRSConfig>::new()
.with_num_players(num_players as usize)
.with_fps(fps as usize)?
.with_input_delay(delay as usize)
.with_sparse_saving_mode(sparse_saving);
// add players
for (handle, player_type) in self.players.drain() {
sess_build = sess_build.add_player(player_type, handle as usize)?;
}
// add spectators
// todo
// start ggrs session
let result = UdpNonBlockingSocket::bind_to_port(local_port);
if result.is_err() {
return Err(GGRSError::InvalidRequest {
info: "couldn't bind to port".to_string(),
});
}
self.p2p_session = Some(sess_build.start_p2p_session(result.unwrap())?);
Ok(())
};
if let Err(err) = create_sess() {
if self.show_debug {
godot_print!("ERROR FROM RUST! {}", err);
}
return false;
}
return true;
}
#[func]
fn events(&mut self) -> Gd<GodotGGRSEventCollection> {
let mut result = Gd::<GodotGGRSEventCollection>::new_default();
let Some(session) = &mut self.p2p_session else { return result; };
for ev in session.events() {
match ev {
ggrs::GGRSEvent::Synchronizing { addr, total, count } => {
let mut event = Gd::<GodotGGRSEvent>::new_default();
event.bind_mut().set(
1000,
Some(GodotString::from(addr.to_string())),
Some(total),
Some(count),
None,
None,
);
result.bind_mut().add_event(event);
}
ggrs::GGRSEvent::Synchronized { addr } => {
let mut event = Gd::<GodotGGRSEvent>::new_default();
event.bind_mut().set(
2000,
Some(GodotString::from(addr.to_string())),
None,
None,
None,
None,
);
result.bind_mut().add_event(event);
}
ggrs::GGRSEvent::NetworkInterrupted {
addr,
disconnect_timeout,
} => {
let mut event = Gd::<GodotGGRSEvent>::new_default();
event.bind_mut().set(
3000,
Some(GodotString::from(addr.to_string())),
None,
None,
Some(disconnect_timeout as u32),
None,
);
result.bind_mut().add_event(event);
}
ggrs::GGRSEvent::NetworkResumed { addr } => {
let mut event = Gd::<GodotGGRSEvent>::new_default();
event.bind_mut().set(
4000,
Some(GodotString::from(addr.to_string())),
None,
None,
None,
None,
);
result.bind_mut().add_event(event);
}
ggrs::GGRSEvent::WaitRecommendation { skip_frames } => {
let mut event = Gd::<GodotGGRSEvent>::new_default();
event
.bind_mut()
.set(5000, None, None, None, None, Some(skip_frames));
result.bind_mut().add_event(event);
}
ggrs::GGRSEvent::Disconnected { addr } => {
let mut event = Gd::<GodotGGRSEvent>::new_default();
event.bind_mut().set(
6000,
Some(GodotString::from(addr.to_string())),
None,
None,
None,
None,
);
result.bind_mut().add_event(event);
}
};
}
return result;
}
#[func]
fn poll_remote_clients(&mut self) {
let Some(session) = &mut self.p2p_session else { return; };
session.poll_remote_clients();
}
// -1000 = SESSION ERROR
#[func]
fn frames_ahead(&self) -> i32 {
let Some(session) = &self.p2p_session else { return -1000 };
session.frames_ahead()
}
/*
1000 = Error
2000 = SessionState::Synchronizing
3000 = SessionState::Running
*/
#[func]
fn current_state(&self) -> u32 {
let Some(session) = &self.p2p_session else { return 1000 };
match session.current_state() {
ggrs::SessionState::Synchronizing => return 2000,
ggrs::SessionState::Running => return 3000,
}
}
#[func]
fn add_local_input(&mut self, handle: i64, input: i32) {
let Some(session) = &mut self.p2p_session else { return };
if session.add_local_input(handle as usize, input).is_err() {
if self.show_debug {
godot_print!("Could not add local input!");
}
}
}
#[func]
fn advance_frame(&mut self) -> Gd<GodotGGRSRequestCollection> {
let mut result = Gd::<GodotGGRSRequestCollection>::new_default();
// handle errors
let Some(session) = &mut self.p2p_session else {
result.bind_mut().set(1000, None, None);
return result;
};
let progress = session.advance_frame();
if let Err(err) = progress {
match err {
GGRSError::PredictionThreshold => {
result.bind_mut().set(2000, None, None);
}
GGRSError::InvalidRequest { info } => {
result.bind_mut().set(3000, None, Some(GodotString::from(info.to_string())));
}
GGRSError::MismatchedChecksum { frame } => {
result.bind_mut().set(4000, Some(frame), None);
}
GGRSError::NotSynchronized => {
result.bind_mut().set(5000, None, None);
}
GGRSError::SpectatorTooFarBehind => {
result.bind_mut().set(6000, None, None);
}
_ => result.bind_mut().set(7000, None, None),
}
return result;
}
// no errors? handle incoming requests
result.bind_mut().set(1000, None, None);
let requests = progress.unwrap();
for req in requests {
let mut request = Gd::<GodotGGRSRequest>::new_default();
match req {
ggrs::GGRSRequest::SaveGameState { cell, frame } => {
request.bind_mut().set(1000, Some(frame), Some(cell));
}
ggrs::GGRSRequest::LoadGameState { cell , frame } => {
request.bind_mut().set(2000, Some(frame), Some(cell));
}
ggrs::GGRSRequest::AdvanceFrame { inputs } => {
request.bind_mut().set(3000, None, None);
for (input, status) in inputs {
let mut new_input = Gd::<GodotGGRSInput>::new_default();
let input_status = match status {
ggrs::InputStatus::Confirmed => 1000,
ggrs::InputStatus::Predicted => 2000,
ggrs::InputStatus::Disconnected => 3000,
};
new_input.bind_mut().set(input, input_status);
request.bind_mut().add_input(new_input);
}
}
}
result.bind_mut().add_request(request);
};
return result;
}
}
#[godot_api]
impl GodotExt for GodotGGRS {
fn init(base: Base<Node>) -> Self {
GodotGGRS {
show_debug: false,
players: HashMap::new(),
p2p_session: None,
base,
}
}
}
#[godot_api]
impl GodotExt for GodotGGRSEventCollection {
fn init(base: Base<RefCounted>) -> Self {
GodotGGRSEventCollection {
events: VecDeque::new(),
base,
}
}
}
#[godot_api]
impl GodotExt for GodotGGRSEvent {
fn init(base: Base<RefCounted>) -> Self {
GodotGGRSEvent {
code: 0,
addr: None,
total: None,
count: None,
disconnect_timeout: None,
skip_frames: None,
base,
}
}
}
#[godot_api]
impl GodotExt for GodotGGRSRequestCollection {
fn init(base: Base<RefCounted>) -> Self {
GodotGGRSRequestCollection {
code: 0,
frame: None,
info: None,
requests: VecDeque::new(),
base,
}
}
}
#[godot_api]
impl GodotExt for GodotGGRSRequest {
fn init(base: Base<RefCounted>) -> Self {
GodotGGRSRequest {
code: 0,
frame: None,
ggrs_cell: None,
inputs: VecDeque::new(),
base,
}
}
}
#[godot_api]
impl GodotExt for GodotGGRSInput {
fn init(base: Base<RefCounted>) -> Self {
GodotGGRSInput {
input: 0,
status: 0,
base: base,
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment