Skip to content

Instantly share code, notes, and snippets.

@jonatino
Last active September 18, 2024 17:02
Show Gist options
  • Save jonatino/3be3273fe8fc9a0d43a879aba7aff62a to your computer and use it in GitHub Desktop.
Save jonatino/3be3273fe8fc9a0d43a879aba7aff62a to your computer and use it in GitHub Desktop.
Update with /u/WorldsBegin solution
#![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