Last active
February 28, 2024 14:44
-
-
Save mingyang91/cbce832f5a6b2fb098bc0e40164964cc to your computer and use it in GitHub Desktop.
Object Pool for Godot
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
impl ReuseObject for Bullet { | |
fn init(&mut self) { | |
} | |
fn prepare(&mut self) { | |
if self.state != State::Init { | |
self.state = State::Init; | |
self.base_mut().set_process(true); | |
self.base_mut().set_physics_process(true); | |
self.base_mut().show(); | |
} | |
} | |
fn recycle(&mut self) { | |
if self.state != State::Inactive { | |
self.state = State::Inactive; | |
self.base_mut().hide(); | |
self.base_mut().set_process(false); | |
self.base_mut().set_physics_process(false); | |
} | |
} | |
} |
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
use std::rc::Rc; | |
use object_pool::Pool; | |
use std::cell::{Ref, RefCell, RefMut}; | |
use std::collections::VecDeque; | |
use std::ops::Deref; | |
use godot::obj::{Bounds, bounds, Gd, GodotClass}; | |
pub trait ReuseObject { | |
fn init(&mut self); | |
fn prepare(&mut self); | |
fn recycle(&mut self); | |
} | |
impl <C> ReuseObject for Gd<C> | |
where C: ReuseObject + GodotClass + Bounds<Declarer = bounds::DeclUser>, { | |
fn init(&mut self) { | |
self.bind_mut().init(); | |
} | |
fn prepare(&mut self) { | |
self.bind_mut().prepare(); | |
} | |
fn recycle(&mut self) { | |
self.bind_mut().recycle(); | |
} | |
} | |
pub struct GdPool<R: ReuseObject> { | |
capacity: usize, | |
pool: Rc<Pool<R>>, | |
buffer: RefCell<VecDeque<Rc<Reuse<R>>>>, | |
} | |
pub struct Reuse<R: ReuseObject> { | |
pool: Rc<Pool<R>>, | |
obj: RefCell<Option<R>> | |
} | |
impl <R: ReuseObject> Reuse<R> { | |
pub fn borrow(&self) -> Ref<R> { | |
Ref::map(self.obj.borrow(), |obj| { | |
obj.as_ref().expect("impossible") | |
}) | |
} | |
pub fn borrow_mut(&self) -> RefMut<R> { | |
RefMut::map(self.obj.borrow_mut(), |obj| { | |
obj.as_mut().expect("impossible") | |
}) | |
} | |
} | |
impl <R> Drop for Reuse<R> | |
where R: ReuseObject { | |
fn drop(&mut self) { | |
if let Some(object) = self.obj.borrow_mut().take() { | |
tracing::debug!("Dropping obj"); | |
self.pool.attach(object); | |
} | |
} | |
} | |
impl <R: ReuseObject> GdPool<R> { | |
pub fn new<F>(capacity: usize, init: F) -> Self | |
where F: Fn() -> R { | |
GdPool { | |
capacity, | |
pool: Rc::new(Pool::new( | |
capacity * 2, | |
|| { | |
let mut obj = init.call(()); | |
ReuseObject::init(&mut obj); | |
obj | |
} | |
)), | |
buffer: RefCell::new(VecDeque::with_capacity(capacity * 2)), | |
} | |
} | |
pub fn get(&self) -> Option<Rc<Reuse<R>>> { | |
let object = self.pool.try_pull()?; | |
let (_, mut obj) = object.detach(); | |
obj.prepare(); | |
tracing::debug!("object prepare"); | |
let reuse = Rc::new(Reuse { | |
pool: self.pool.clone(), | |
obj: RefCell::new(Some(obj)), | |
}); | |
let mut buf = self.buffer.borrow_mut(); | |
buf.push_front(reuse.clone()); | |
if buf.len() > self.capacity { | |
tracing::debug!("buffer full {}, drop older object", buf.len()); | |
let drain: Vec<Rc<Reuse<R>>> = buf | |
.drain(self.capacity..) | |
.collect(); | |
for reuse in drain { | |
tracing::debug!("destroy object"); | |
reuse.obj | |
.borrow_mut() | |
.as_mut() | |
.expect("impossible") | |
.recycle() | |
} | |
} | |
Some(reuse) | |
} | |
} |
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
fn process(&mut self) { | |
if input.is_action_just_released("attack".into()) { | |
let start = self.base().get_global_position(); | |
let mut bullet = self.create_bullet(); | |
let mut bullet = bullet.borrow_mut(); | |
bullet.bind_mut().shot(start, velocity); | |
tracing::debug!("bullet shot"); | |
} | |
} | |
fn ready(&mut self) { | |
let bullet_scene = load::<PackedScene>("res://Bullet.tscn"); | |
let base = self.base().clone(); | |
let pool = GdPool::new(10, || { | |
tracing::debug!("Instantiate bullet"); | |
let bullet = bullet_scene.instantiate_as::<Bullet>(); | |
let Some(mut parent) = base.get_parent() else { | |
tracing::error!("Failed to get parent of bullet"); | |
unreachable!("Failed to get parent of bullet"); | |
}; | |
parent.call_deferred("add_child".into(), &[bullet.clone().to_variant()]); | |
bullet | |
}); | |
if let Err(_) = self.bullet_pool.set(pool) { | |
tracing::error!("Failed to create bullet pool!"); | |
} | |
} | |
fn create_bullet(&mut self) -> Rc<Reuse<Gd<Bullet>>> { | |
let pool = self.bullet_pool | |
.get_mut() | |
.expect("uninitialized bullet pool"); | |
let bullet = pool.get() | |
.expect("pool exhausted") | |
.clone(); | |
return bullet | |
} | |
#[derive(GodotClass)] | |
#[class(base=Area2D)] | |
pub struct Player { | |
bullet_pool: OnceCell<GdPool<Gd<Bullet>>>, | |
base: Base<Area2D>, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment