Lyric Game Engine
After taking some time to read over a few rust books and learning the BEVY game engine I've decided I want to take a shot at making my own game engine. Eventually I might even get to a point where I can release games made in said engine, thats the hope anyways. Now to get straight to the point coding isn't something I'm new to and out of the many languages I have used rust by far is my favorite, yet fighting with the borrow checker is hell. Regardless for the past few hours I've started to make the basics and I've reached a standstill.
Table 1-0: File Structure
App Design Pattern
To start these are the necessary files.
Filename: src/prelude/app/mod.rs
use std::rc::Rc;
use std::cell::RefCell;
use std::sync::Arc;
use super::Plugin;
mod thread_pool;
use thread_pool::ThreadPool;
pub enum Process {
Init,
Update,
}
trait System: 'static + Send + Sync {
fn execute(&self);
}
impl<T> System for T
where
T: 'static + Fn() + Send + Sync,
{
fn execute(&self) {
self();
}
}
enum Task {
Init(Arc<dyn System>),
Update(Arc<dyn System>),
}
struct Schedule {
tasks: Vec<Task>,
plugins: Vec<Box<dyn Plugin>>,
}
impl Schedule {
fn new() -> Schedule {
Schedule {
tasks: vec![],
plugins: vec![],
}
}
fn add_plugins<T>(&mut self, plugins: Vec<T>)
where
T: Plugin,
{
self.plugins.extend(
plugins
.into_iter()
.map(|plugin| Box::new(plugin) as Box<dyn Plugin>),
);
}
fn add_systems<T>(&mut self, process: Process, systems: Vec<T>)
where
T: System,
{
println!("added system!");
self.tasks.extend(systems.into_iter().map(|system| {
let system = Arc::new(system);
match process {
Process::Init => Task::Init(system),
Process::Update => Task::Update(system),
}
}));
}
fn run(&self, app: Rc<RefCell<&mut App>>) {
for plugin in &self.plugins {
plugin.build(&mut app.borrow_mut());
}
let pool = ThreadPool::new(4);
for task in &self.tasks {
if let Task::Init(system) = task {
let system = Arc::clone(system);
pool.execute(move || {
system.execute();
});
}
}
loop {
for task in &self.tasks {
if let Task::Update(system) = task {
let system = Arc::clone(system);
pool.execute(move || {
system.execute();
});
}
}
}
}
}
pub struct App {
schedule: Schedule,
}
impl App {
pub fn new() -> App {
App {
schedule: Schedule::new(),
}
}
pub fn add_plugins<T>(&mut self, plugins: Vec<T>) -> &mut Self
where
T: Plugin,
{
self.schedule.add_plugins(plugins);
self
}
pub fn add_systems<T>(&mut self, process: Process, systems: Vec<T>) -> &mut Self
where
T: System,
{
self.schedule.add_systems(process, systems);
self
}
pub fn run(&mut self) {
let app = Rc::new(RefCell::new(self));
self.schedule.run(Rc::clone(&app));
}
}
Filename: src/prelude/app/thread_pool/mod.rs
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread;
use super::System;
type Task = Box<dyn System>;
pub struct ThreadPool {
workers: Vec<Worker>,
sender: Sender<Task>,
}
impl ThreadPool {
pub fn new(size: usize) -> ThreadPool {
let (sender, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut workers = Vec::new();
for _i in 0..size {
workers.push(Worker::new(Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn execute<T>(&self, task: T)
where
T: System,
{
let task = Box::new(task);
self.sender
.send(task)
.expect("Failed to send task to thread pool")
}
}
struct Worker {
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(receiver: Arc<Mutex<Receiver<Task>>>) -> Worker {
let thread = thread::spawn(move || loop {
let task = receiver.lock().unwrap().recv().unwrap();
task.execute();
});
Worker {
thread: Some(thread),
}
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
for worker in self.workers.iter_mut() {
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
Filename: src/main.rs
use std::thread;
use std::time::Duration;
use lyric_game_engine::prelude::*;
fn greeting() {
println!("hello!");
}
fn farewell() {
println!("goodbye!");
thread::sleep(Duration::from_secs(3));
}
struct MyPlugin;
impl Plugin for MyPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Process::Update, vec![farewell]);
}
}
fn main() {
App::new()
.add_plugins(vec![MyPlugin])
.add_systems(Process::Init, vec![greeting])
.run();
}
Problems
-
Plugins don't work correctly because of the way they get a reference to the app struct, I get an already borrowed error
-
And parallelism works for the most part except that the farewell function gets run 4 times instead of 1