mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
add support for custom suggestions in azalea-brigadier and cleanup a bit
This commit is contained in:
parent
3d340f585a
commit
da73b4316d
12 changed files with 155 additions and 117 deletions
|
@ -17,25 +17,25 @@ impl ArgumentType for Double {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
let start = reader.cursor;
|
let start = reader.cursor;
|
||||||
let result = reader.read_double()?;
|
let result = reader.read_double()?;
|
||||||
if let Some(minimum) = self.minimum {
|
if let Some(minimum) = self.minimum
|
||||||
if result < minimum {
|
&& result < minimum
|
||||||
reader.cursor = start;
|
{
|
||||||
return Err(BuiltInExceptions::DoubleTooSmall {
|
reader.cursor = start;
|
||||||
found: result,
|
return Err(BuiltInExceptions::DoubleTooSmall {
|
||||||
min: minimum,
|
found: result,
|
||||||
}
|
min: minimum,
|
||||||
.create_with_context(reader));
|
|
||||||
}
|
}
|
||||||
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
if let Some(maximum) = self.maximum {
|
if let Some(maximum) = self.maximum
|
||||||
if result > maximum {
|
&& result > maximum
|
||||||
reader.cursor = start;
|
{
|
||||||
return Err(BuiltInExceptions::DoubleTooBig {
|
reader.cursor = start;
|
||||||
found: result,
|
return Err(BuiltInExceptions::DoubleTooBig {
|
||||||
max: maximum,
|
found: result,
|
||||||
}
|
max: maximum,
|
||||||
.create_with_context(reader));
|
|
||||||
}
|
}
|
||||||
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
Ok(Arc::new(result))
|
Ok(Arc::new(result))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,25 +17,25 @@ impl ArgumentType for Float {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
let start = reader.cursor;
|
let start = reader.cursor;
|
||||||
let result = reader.read_float()?;
|
let result = reader.read_float()?;
|
||||||
if let Some(minimum) = self.minimum {
|
if let Some(minimum) = self.minimum
|
||||||
if result < minimum {
|
&& result < minimum
|
||||||
reader.cursor = start;
|
{
|
||||||
return Err(BuiltInExceptions::FloatTooSmall {
|
reader.cursor = start;
|
||||||
found: result,
|
return Err(BuiltInExceptions::FloatTooSmall {
|
||||||
min: minimum,
|
found: result,
|
||||||
}
|
min: minimum,
|
||||||
.create_with_context(reader));
|
|
||||||
}
|
}
|
||||||
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
if let Some(maximum) = self.maximum {
|
if let Some(maximum) = self.maximum
|
||||||
if result > maximum {
|
&& result > maximum
|
||||||
reader.cursor = start;
|
{
|
||||||
return Err(BuiltInExceptions::FloatTooBig {
|
reader.cursor = start;
|
||||||
found: result,
|
return Err(BuiltInExceptions::FloatTooBig {
|
||||||
max: maximum,
|
found: result,
|
||||||
}
|
max: maximum,
|
||||||
.create_with_context(reader));
|
|
||||||
}
|
}
|
||||||
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
Ok(Arc::new(result))
|
Ok(Arc::new(result))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,25 +17,25 @@ impl ArgumentType for Integer {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
let start = reader.cursor;
|
let start = reader.cursor;
|
||||||
let result = reader.read_int()?;
|
let result = reader.read_int()?;
|
||||||
if let Some(minimum) = self.minimum {
|
if let Some(minimum) = self.minimum
|
||||||
if result < minimum {
|
&& result < minimum
|
||||||
reader.cursor = start;
|
{
|
||||||
return Err(BuiltInExceptions::IntegerTooSmall {
|
reader.cursor = start;
|
||||||
found: result,
|
return Err(BuiltInExceptions::IntegerTooSmall {
|
||||||
min: minimum,
|
found: result,
|
||||||
}
|
min: minimum,
|
||||||
.create_with_context(reader));
|
|
||||||
}
|
}
|
||||||
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
if let Some(maximum) = self.maximum {
|
if let Some(maximum) = self.maximum
|
||||||
if result > maximum {
|
&& result > maximum
|
||||||
reader.cursor = start;
|
{
|
||||||
return Err(BuiltInExceptions::IntegerTooBig {
|
reader.cursor = start;
|
||||||
found: result,
|
return Err(BuiltInExceptions::IntegerTooBig {
|
||||||
max: maximum,
|
found: result,
|
||||||
}
|
max: maximum,
|
||||||
.create_with_context(reader));
|
|
||||||
}
|
}
|
||||||
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
Ok(Arc::new(result))
|
Ok(Arc::new(result))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,25 +17,25 @@ impl ArgumentType for Long {
|
||||||
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
fn parse(&self, reader: &mut StringReader) -> Result<Arc<dyn Any>, CommandSyntaxException> {
|
||||||
let start = reader.cursor;
|
let start = reader.cursor;
|
||||||
let result = reader.read_long()?;
|
let result = reader.read_long()?;
|
||||||
if let Some(minimum) = self.minimum {
|
if let Some(minimum) = self.minimum
|
||||||
if result < minimum {
|
&& result < minimum
|
||||||
reader.cursor = start;
|
{
|
||||||
return Err(BuiltInExceptions::LongTooSmall {
|
reader.cursor = start;
|
||||||
found: result,
|
return Err(BuiltInExceptions::LongTooSmall {
|
||||||
min: minimum,
|
found: result,
|
||||||
}
|
min: minimum,
|
||||||
.create_with_context(reader));
|
|
||||||
}
|
}
|
||||||
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
if let Some(maximum) = self.maximum {
|
if let Some(maximum) = self.maximum
|
||||||
if result > maximum {
|
&& result > maximum
|
||||||
reader.cursor = start;
|
{
|
||||||
return Err(BuiltInExceptions::LongTooBig {
|
reader.cursor = start;
|
||||||
found: result,
|
return Err(BuiltInExceptions::LongTooBig {
|
||||||
max: maximum,
|
found: result,
|
||||||
}
|
max: maximum,
|
||||||
.create_with_context(reader));
|
|
||||||
}
|
}
|
||||||
|
.create_with_context(reader));
|
||||||
}
|
}
|
||||||
Ok(Arc::new(result))
|
Ok(Arc::new(result))
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,20 @@ use crate::{
|
||||||
tree::{Command, CommandNode},
|
tree::{Command, CommandNode},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub enum ArgumentBuilderType {
|
pub enum ArgumentBuilderType<S> {
|
||||||
Literal(Literal),
|
Literal(Literal),
|
||||||
Argument(Argument),
|
Argument(Argument<S>),
|
||||||
|
}
|
||||||
|
impl<S> Clone for ArgumentBuilderType<S> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
ArgumentBuilderType::Literal(literal) => ArgumentBuilderType::Literal(literal.clone()),
|
||||||
|
ArgumentBuilderType::Argument(argument) => {
|
||||||
|
ArgumentBuilderType::Argument(argument.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node that hasn't yet been built.
|
/// A node that hasn't yet been built.
|
||||||
|
@ -30,7 +40,7 @@ pub struct ArgumentBuilder<S> {
|
||||||
|
|
||||||
/// A node that isn't yet built.
|
/// A node that isn't yet built.
|
||||||
impl<S> ArgumentBuilder<S> {
|
impl<S> ArgumentBuilder<S> {
|
||||||
pub fn new(value: ArgumentBuilderType) -> Self {
|
pub fn new(value: ArgumentBuilderType<S>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
arguments: CommandNode {
|
arguments: CommandNode {
|
||||||
value,
|
value,
|
||||||
|
|
|
@ -12,7 +12,7 @@ impl Literal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Literal> for ArgumentBuilderType {
|
impl<S> From<Literal> for ArgumentBuilderType<S> {
|
||||||
fn from(literal: Literal) -> Self {
|
fn from(literal: Literal) -> Self {
|
||||||
Self::Literal(literal)
|
Self::Literal(literal)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,35 @@
|
||||||
use std::{any::Any, fmt::Debug, sync::Arc};
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
fmt::{self, Debug},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
|
use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType};
|
||||||
use crate::{
|
use crate::{
|
||||||
arguments::ArgumentType,
|
arguments::ArgumentType,
|
||||||
|
context::CommandContext,
|
||||||
exceptions::CommandSyntaxException,
|
exceptions::CommandSyntaxException,
|
||||||
string_reader::StringReader,
|
string_reader::StringReader,
|
||||||
suggestion::{Suggestions, SuggestionsBuilder},
|
suggestion::{SuggestionProvider, Suggestions, SuggestionsBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An argument node type. The `T` type parameter is the type of the argument,
|
/// An argument node type. The `T` type parameter is the type of the argument,
|
||||||
/// which can be anything.
|
/// which can be anything.
|
||||||
#[derive(Clone)]
|
pub struct Argument<S> {
|
||||||
pub struct Argument {
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
parser: Arc<dyn ArgumentType + Send + Sync>,
|
parser: Arc<dyn ArgumentType + Send + Sync>,
|
||||||
|
custom_suggestions: Option<Arc<dyn SuggestionProvider<S> + Send + Sync>>,
|
||||||
}
|
}
|
||||||
impl Argument {
|
impl<S> Argument<S> {
|
||||||
pub fn new(name: &str, parser: Arc<dyn ArgumentType + Send + Sync>) -> Self {
|
pub fn new(
|
||||||
|
name: &str,
|
||||||
|
parser: Arc<dyn ArgumentType + Send + Sync>,
|
||||||
|
custom_suggestions: Option<Arc<dyn SuggestionProvider<S> + Send + Sync>>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
parser,
|
parser,
|
||||||
|
custom_suggestions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,11 +37,16 @@ impl Argument {
|
||||||
self.parser.parse(reader)
|
self.parser.parse(reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_suggestions(&self, builder: SuggestionsBuilder) -> Suggestions {
|
pub fn list_suggestions(
|
||||||
// TODO: custom suggestions
|
&self,
|
||||||
// https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/tree/ArgumentCommandNode.java#L71
|
context: CommandContext<S>,
|
||||||
|
builder: SuggestionsBuilder,
|
||||||
self.parser.list_suggestions(builder)
|
) -> Suggestions {
|
||||||
|
if let Some(s) = &self.custom_suggestions {
|
||||||
|
s.get_suggestions(context, builder)
|
||||||
|
} else {
|
||||||
|
self.parser.list_suggestions(builder)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn examples(&self) -> Vec<String> {
|
pub fn examples(&self) -> Vec<String> {
|
||||||
|
@ -39,14 +54,14 @@ impl Argument {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Argument> for ArgumentBuilderType {
|
impl<S> From<Argument<S>> for ArgumentBuilderType<S> {
|
||||||
fn from(argument: Argument) -> Self {
|
fn from(argument: Argument<S>) -> Self {
|
||||||
Self::Argument(argument)
|
Self::Argument(argument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Argument {
|
impl<S> Debug for Argument<S> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Argument")
|
f.debug_struct("Argument")
|
||||||
.field("name", &self.name)
|
.field("name", &self.name)
|
||||||
// .field("parser", &self.parser)
|
// .field("parser", &self.parser)
|
||||||
|
@ -59,5 +74,15 @@ pub fn argument<S>(
|
||||||
name: &str,
|
name: &str,
|
||||||
parser: impl ArgumentType + Send + Sync + 'static,
|
parser: impl ArgumentType + Send + Sync + 'static,
|
||||||
) -> ArgumentBuilder<S> {
|
) -> ArgumentBuilder<S> {
|
||||||
ArgumentBuilder::new(Argument::new(name, Arc::new(parser)).into())
|
ArgumentBuilder::new(Argument::new(name, Arc::new(parser), None).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Clone for Argument<S> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
name: self.name.clone(),
|
||||||
|
parser: self.parser.clone(),
|
||||||
|
custom_suggestions: self.custom_suggestions.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,23 +54,22 @@ impl CommandSyntaxException {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context(&self) -> Option<String> {
|
pub fn context(&self) -> Option<String> {
|
||||||
if let Some(input) = &self.input {
|
if let Some(input) = &self.input
|
||||||
if let Some(cursor) = self.cursor {
|
&& let Some(cursor) = self.cursor
|
||||||
let mut builder = String::new();
|
{
|
||||||
let cursor = cmp::min(input.len(), cursor);
|
let mut builder = String::new();
|
||||||
|
let cursor = cmp::min(input.len(), cursor);
|
||||||
|
|
||||||
if cursor > CONTEXT_AMOUNT {
|
if cursor > CONTEXT_AMOUNT {
|
||||||
builder.push_str("...");
|
builder.push_str("...");
|
||||||
}
|
|
||||||
|
|
||||||
builder.push_str(
|
|
||||||
&input
|
|
||||||
[(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
|
|
||||||
);
|
|
||||||
builder.push_str("<--[HERE]");
|
|
||||||
|
|
||||||
return Some(builder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.push_str(
|
||||||
|
&input[(cmp::max(0, cursor as isize - CONTEXT_AMOUNT as isize) as usize)..cursor],
|
||||||
|
);
|
||||||
|
builder.push_str("<--[HERE]");
|
||||||
|
|
||||||
|
return Some(builder);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod suggestion_provider;
|
||||||
mod suggestions;
|
mod suggestions;
|
||||||
mod suggestions_builder;
|
mod suggestions_builder;
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ use std::{
|
||||||
use azalea_buf::AzaleaWrite;
|
use azalea_buf::AzaleaWrite;
|
||||||
#[cfg(feature = "azalea-buf")]
|
#[cfg(feature = "azalea-buf")]
|
||||||
use azalea_chat::FormattedText;
|
use azalea_chat::FormattedText;
|
||||||
|
pub use suggestion_provider::SuggestionProvider;
|
||||||
pub use suggestions::Suggestions;
|
pub use suggestions::Suggestions;
|
||||||
pub use suggestions_builder::SuggestionsBuilder;
|
pub use suggestions_builder::SuggestionsBuilder;
|
||||||
|
|
||||||
|
|
10
azalea-brigadier/src/suggestion/suggestion_provider.rs
Normal file
10
azalea-brigadier/src/suggestion/suggestion_provider.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use super::{Suggestions, SuggestionsBuilder};
|
||||||
|
use crate::context::CommandContext;
|
||||||
|
|
||||||
|
pub trait SuggestionProvider<S> {
|
||||||
|
fn get_suggestions(
|
||||||
|
&self,
|
||||||
|
context: CommandContext<S>,
|
||||||
|
builder: SuggestionsBuilder,
|
||||||
|
) -> Suggestions;
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ pub type Command<S> = Option<Arc<dyn Fn(&CommandContext<S>) -> i32 + Send + Sync
|
||||||
/// An ArgumentBuilder that has been built.
|
/// An ArgumentBuilder that has been built.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct CommandNode<S> {
|
pub struct CommandNode<S> {
|
||||||
pub value: ArgumentBuilderType,
|
pub value: ArgumentBuilderType<S>,
|
||||||
|
|
||||||
// this is a BTreeMap because children need to be ordered when getting command suggestions
|
// this is a BTreeMap because children need to be ordered when getting command suggestions
|
||||||
pub children: BTreeMap<String, Arc<RwLock<CommandNode<S>>>>,
|
pub children: BTreeMap<String, Arc<RwLock<CommandNode<S>>>>,
|
||||||
|
@ -66,7 +66,7 @@ impl<S> CommandNode<S> {
|
||||||
}
|
}
|
||||||
/// Gets the argument, or panics. You should use match if you're not certain
|
/// Gets the argument, or panics. You should use match if you're not certain
|
||||||
/// about the type.
|
/// about the type.
|
||||||
pub fn argument(&self) -> &Argument {
|
pub fn argument(&self) -> &Argument<S> {
|
||||||
match self.value {
|
match self.value {
|
||||||
ArgumentBuilderType::Argument(ref argument) => argument,
|
ArgumentBuilderType::Argument(ref argument) => argument,
|
||||||
_ => panic!("CommandNode::argument() called on non-argument node"),
|
_ => panic!("CommandNode::argument() called on non-argument node"),
|
||||||
|
@ -214,9 +214,7 @@ impl<S> CommandNode<S> {
|
||||||
|
|
||||||
pub fn list_suggestions(
|
pub fn list_suggestions(
|
||||||
&self,
|
&self,
|
||||||
// context is here because that's how it is in mojang's brigadier, but we haven't
|
context: CommandContext<S>,
|
||||||
// implemented custom suggestions yet so this is unused rn
|
|
||||||
_context: CommandContext<S>,
|
|
||||||
builder: SuggestionsBuilder,
|
builder: SuggestionsBuilder,
|
||||||
) -> Suggestions {
|
) -> Suggestions {
|
||||||
match &self.value {
|
match &self.value {
|
||||||
|
@ -231,7 +229,7 @@ impl<S> CommandNode<S> {
|
||||||
Suggestions::default()
|
Suggestions::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ArgumentBuilderType::Argument(argument) => argument.list_suggestions(builder),
|
ArgumentBuilderType::Argument(argument) => argument.list_suggestions(context, builder),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,7 +237,7 @@ impl<S> CommandNode<S> {
|
||||||
impl<S> Debug for CommandNode<S> {
|
impl<S> Debug for CommandNode<S> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("CommandNode")
|
f.debug_struct("CommandNode")
|
||||||
.field("value", &self.value)
|
// .field("value", &self.value)
|
||||||
.field("children", &self.children)
|
.field("children", &self.children)
|
||||||
.field("command", &self.command.is_some())
|
.field("command", &self.command.is_some())
|
||||||
// .field("requirement", &self.requirement)
|
// .field("requirement", &self.requirement)
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use azalea_brigadier::{
|
use azalea_brigadier::prelude::*;
|
||||||
arguments::integer_argument_type::integer,
|
|
||||||
builder::{literal_argument_builder::literal, required_argument_builder::argument},
|
|
||||||
command_dispatcher::CommandDispatcher,
|
|
||||||
context::CommandContext,
|
|
||||||
};
|
|
||||||
use bevy_app::App;
|
use bevy_app::App;
|
||||||
use bevy_ecs::{prelude::*, system::RunSystemOnce};
|
use bevy_ecs::{prelude::*, system::RunSystemOnce};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -145,8 +140,7 @@ impl DispatchStorage {
|
||||||
///
|
///
|
||||||
/// Spawns a number of entities with the [`SpawnedEntity`] component.
|
/// Spawns a number of entities with the [`SpawnedEntity`] component.
|
||||||
fn command_spawn_entity_num(context: &CommandContext<WorldAccessor>) -> i32 {
|
fn command_spawn_entity_num(context: &CommandContext<WorldAccessor>) -> i32 {
|
||||||
let num = context.argument("entities").unwrap();
|
let num = get_integer(context, "entities").unwrap();
|
||||||
let num = *num.downcast_ref::<i32>().unwrap();
|
|
||||||
|
|
||||||
for _ in 0..num {
|
for _ in 0..num {
|
||||||
context.source.lock().spawn(SpawnedEntity);
|
context.source.lock().spawn(SpawnedEntity);
|
||||||
|
@ -187,7 +181,7 @@ impl WorldAccessor {
|
||||||
struct SpawnedEntity;
|
struct SpawnedEntity;
|
||||||
|
|
||||||
/// Implemented for convenience.
|
/// Implemented for convenience.
|
||||||
impl std::ops::Deref for WorldAccessor {
|
impl Deref for WorldAccessor {
|
||||||
type Target = Arc<Mutex<World>>;
|
type Target = Arc<Mutex<World>>;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.world
|
&self.world
|
||||||
|
|
Loading…
Add table
Reference in a new issue