use std::fmt::{self, Debug, Formatter};
use std::mem;
use crate::builtin::{GString, NodePath};
use crate::classes::{Node, Resource};
use crate::meta::shape::GodotShape;
use crate::meta::{AsArg, FromGodot, GodotConvert, arg_into_owned};
use crate::obj::{Gd, Inherits};
use crate::registry::property::Var;
#[derive(Debug)]
pub struct OnReady<T> {
state: InitState<T>,
}
impl<T: Inherits<Node>> OnReady<Gd<T>> {
pub fn from_node(path: impl AsArg<NodePath>) -> Self {
arg_into_owned!(path);
Self::from_base_fn(move |base| base.get_node_as(&path))
}
}
impl<T: Inherits<Resource>> OnReady<Gd<T>> {
pub fn from_loaded(path: impl AsArg<GString>) -> Self {
arg_into_owned!(path);
Self::new(move || crate::tools::load(&path))
}
}
impl<T> OnReady<T> {
pub fn new<F>(init_fn: F) -> Self
where
F: FnOnce() -> T + 'static,
{
Self::from_base_fn(|_| init_fn())
}
pub fn from_base_fn<F>(init_fn: F) -> Self
where
F: FnOnce(&Gd<Node>) -> T + 'static,
{
Self {
state: InitState::AutoPrepared {
initializer: Box::new(init_fn),
},
}
}
pub fn manual() -> Self {
Self {
state: InitState::ManualUninitialized,
}
}
pub fn init(&mut self, value: T) {
match &self.state {
InitState::ManualUninitialized => {
self.state = InitState::Initialized { value };
}
InitState::AutoPrepared { .. } | InitState::AutoInitializationFailed => {
panic!("cannot call init() on auto-initialized OnReady objects")
}
InitState::Initialized { .. } => {
panic!("already initialized; did you call init() more than once?")
}
};
}
pub(crate) fn init_auto(&mut self, base: &Gd<Node>) {
match &self.state {
InitState::ManualUninitialized => return, InitState::AutoPrepared { .. } => {} InitState::AutoInitializationFailed => {
panic!("OnReady automatic value initialization has already failed")
}
InitState::Initialized { .. } => panic!("OnReady object already initialized"),
};
let InitState::AutoPrepared { initializer } =
mem::replace(&mut self.state, InitState::AutoInitializationFailed)
else {
unsafe { std::hint::unreachable_unchecked() }
};
self.state = InitState::Initialized {
value: initializer(base),
};
}
}
impl<T> std::ops::Deref for OnReady<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match &self.state {
InitState::ManualUninitialized => {
panic!("OnReady manual value uninitialized, did you call init()?")
}
InitState::AutoPrepared { .. } => {
panic!("OnReady automatic value uninitialized, is only available in ready()")
}
InitState::AutoInitializationFailed => {
panic!("OnReady automatic value initialization failed")
}
InitState::Initialized { value } => value,
}
}
}
impl<T> std::ops::DerefMut for OnReady<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match &mut self.state {
InitState::Initialized { value } => value,
InitState::ManualUninitialized | InitState::AutoPrepared { .. } => {
panic!("value not yet initialized")
}
InitState::AutoInitializationFailed => {
panic!("OnReady automatic value initialization failed")
}
}
}
}
impl<T: GodotConvert> GodotConvert for OnReady<T> {
type Via = T::Via;
fn godot_shape() -> GodotShape {
T::godot_shape()
}
}
impl<T> Var for OnReady<T>
where
T: Var<PubType = T> + FromGodot,
{
type PubType = T;
fn var_get(field: &Self) -> Self::Via {
let deref: &T = field;
T::var_get(deref)
}
fn var_set(field: &mut Self, value: Self::Via) {
let deref: &mut T = field;
T::var_set(deref, value);
}
fn var_pub_get(field: &Self) -> Self::PubType {
let deref: &T = field;
T::var_pub_get(deref)
}
fn var_pub_set(field: &mut Self, value: Self::PubType) {
let deref: &mut T = field;
T::var_pub_set(deref, value);
}
}
type InitFn<T> = dyn FnOnce(&Gd<Node>) -> T;
enum InitState<T> {
ManualUninitialized,
AutoPrepared { initializer: Box<InitFn<T>> },
AutoInitializationFailed,
Initialized { value: T },
}
impl<T: Debug> Debug for InitState<T> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
match self {
InitState::ManualUninitialized => fmt.debug_struct("ManualUninitialized").finish(),
InitState::AutoPrepared { .. } => {
fmt.debug_struct("AutoPrepared").finish_non_exhaustive()
}
InitState::AutoInitializationFailed => {
fmt.debug_struct("AutoInitializationFailed").finish()
}
InitState::Initialized { value } => fmt
.debug_struct("Initialized")
.field("value", value)
.finish(),
}
}
}