Last active
September 18, 2024 17:02
-
-
Save jonatino/3be3273fe8fc9a0d43a879aba7aff62a to your computer and use it in GitHub Desktop.
Update with /u/WorldsBegin solution
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)] | |
use std::collections::{HashMap, VecDeque}; | |
use std::ops::{Coroutine, CoroutineState}; | |
use std::pin::Pin; | |
use std::time::{Duration, SystemTime, UNIX_EPOCH}; | |
type PlayerScript = Pin<Box<dyn Coroutine<&'static mut Player, Yield = f32, Return = ()>>>; | |
pub struct PlayerTask { | |
owner_id: i32, | |
suspended_until: i128, | |
coroutine: PlayerScript, | |
} | |
impl PlayerTask { | |
fn is_suspended(&self) -> bool { | |
self.suspended_until > 0 && current_time_millis() < self.suspended_until | |
} | |
} | |
pub struct Player { | |
pub id: i32, | |
pub name: String, | |
pub x: f32, | |
pub y: f32, | |
} | |
impl Drop for Player { | |
fn drop(&mut self) { | |
//panic!("Player has been dropped") | |
} | |
} | |
#[derive(Default)] | |
pub struct GameWorld { | |
players: HashMap<i32, Player>, | |
tasks: VecDeque<PlayerTask>, | |
} | |
impl GameWorld { | |
pub fn add_player(&mut self, id: i32, name: String, x: f32, y: f32) { | |
self.players.insert(id, Player { id, name, x, y }); | |
} | |
pub fn start_player_task(&mut self, player_key: i32, script: PlayerScript) { | |
let task = PlayerTask { | |
owner_id: player_key, | |
suspended_until: -1, | |
coroutine: script, | |
}; | |
self.tasks.push_back(task) | |
} | |
pub fn abort_task(&mut self, player_key: i32) { | |
// TODO make sure a task cant be aborted during | |
self.tasks.retain(|task| task.owner_id != player_key); | |
} | |
pub fn tick(&mut self) { | |
let (tasks, players) = (&mut self.tasks, &mut self.players); | |
//println!("Tick players={} tasks={}", players.len(), tasks.len()); | |
tasks.retain_mut(|task| { | |
if task.is_suspended() { | |
true // Nothing to do, task is suspended. Retain it, we're executing it later | |
} else if let Some(player) = players.get_mut(&task.owner_id) { | |
println!("Executing task for player id={:?}", task.owner_id); | |
// Pin it | |
let pin: Pin<&mut PlayerScript> = Pin::new(&mut task.coroutine); | |
match unsafe { | |
// Since we know and control EXACTLY how these coroutines are executed, this isn't actually unsafe. | |
// Why do we know this? For the following reasons | |
// 1. Our engine is single threaded. We execute tasks one at a time and only once we have full ownership of the task owner. | |
// 2. Tasks will never execute when the owner doesn't exist | |
// 3. Coroutines only exist in 2 states. | |
// 1. Completed (the borrow is dropped anyways) | |
// 2. Yielded (the borrow is still active but we can guarantee its not in use since the coroutine is not doing anything in a yielded state) | |
// | |
// Having said all that, I think this should be functional and *safe* in theory. | |
let r: &'static mut _ = &mut *(player as *mut _); | |
pin.resume(r) | |
} { | |
CoroutineState::Yielded(seconds) => { | |
println!("yielded for {:?} seconds", seconds); | |
if seconds > 0.0 { | |
task.suspended_until = | |
current_time_millis() + (seconds * 1000.0) as i128; | |
} | |
// Now we manually re-borrow the player pointer and insert into players | |
true | |
} | |
CoroutineState::Complete(()) => false, | |
} | |
} else { | |
// Player no longer exists, abort task and remove from list | |
println!("Player doesnt exist {:?}. Removing task.", task.owner_id); | |
false // Remove it. There's nothing to execute without a context, and things will get nasty | |
} | |
}); | |
} | |
} | |
fn main() { | |
let mut world = GameWorld::default(); | |
let player_id = 1337; | |
// Create a player | |
world.add_player(player_id, String::from("Player1"), 0.0, 0.0); | |
// Start task | |
world.start_player_task( | |
player_id, | |
Box::pin( | |
#[coroutine] | |
static |mut player: &'static mut Player| { | |
loop { | |
println!("Hello name=\"{}\" id={}", player.name, player.id); | |
player = yield 1.; //thread_rng().gen_range(0.3..1.9); | |
println!("Bye name=\"{}\" id={}", player.name, player.id); | |
player = yield 1.; //thread_rng().gen_range(0.3..1.9); | |
} | |
}, | |
), | |
); | |
// Start game engine | |
for _ in 0..10 { | |
world.tick(); | |
// Wait for next tick | |
std::thread::sleep(Duration::from_millis(600)); | |
} | |
} | |
fn current_time_millis() -> i128 { | |
SystemTime::now() | |
.duration_since(UNIX_EPOCH) | |
.unwrap() | |
.as_millis() as i128 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment