mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 23:44:38 +00:00
make physicsstate its own component
This commit is contained in:
parent
1b83277a0c
commit
473b9f9297
5 changed files with 141 additions and 104 deletions
|
@ -1,6 +1,6 @@
|
||||||
pub use crate::chat::ChatPacket;
|
pub use crate::chat::ChatPacket;
|
||||||
use crate::{
|
use crate::{
|
||||||
local_player::{death_event, send_tick_event, update_in_loaded_chunk, LocalPlayer},
|
local_player::{death_event, send_tick_event, update_in_loaded_chunk, LocalPlayer, PhysicsState},
|
||||||
movement::{local_player_ai_step, send_position},
|
movement::{local_player_ai_step, send_position},
|
||||||
packet_handling,
|
packet_handling,
|
||||||
plugins::PluginStates,
|
plugins::PluginStates,
|
||||||
|
@ -223,7 +223,7 @@ impl Client {
|
||||||
local_player.tasks.push(write_packets_task);
|
local_player.tasks.push(write_packets_task);
|
||||||
|
|
||||||
ecs.entity_mut(entity)
|
ecs.entity_mut(entity)
|
||||||
.insert((local_player, packet_receiver));
|
.insert((local_player, packet_receiver, PhysicsState::default()));
|
||||||
|
|
||||||
// just start up the game loop and we're ready!
|
// just start up the game loop and we're ready!
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,6 @@ pub struct LocalPlayer {
|
||||||
|
|
||||||
pub packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>,
|
pub packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>,
|
||||||
|
|
||||||
// pub world: Arc<RwLock<PartialWorld>>,
|
|
||||||
pub physics_state: PhysicsState,
|
|
||||||
pub client_information: ClientInformation,
|
pub client_information: ClientInformation,
|
||||||
/// A map of player uuids to their information in the tab list
|
/// A map of player uuids to their information in the tab list
|
||||||
pub players: HashMap<Uuid, PlayerInfo>,
|
pub players: HashMap<Uuid, PlayerInfo>,
|
||||||
|
@ -43,7 +41,9 @@ pub struct LocalPlayer {
|
||||||
pub(crate) tasks: Vec<JoinHandle<()>>,
|
pub(crate) tasks: Vec<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
/// Component for entities that can move and sprint. Usually only in
|
||||||
|
/// [`LocalPlayer`] entities.
|
||||||
|
#[derive(Default, Component)]
|
||||||
pub struct PhysicsState {
|
pub struct PhysicsState {
|
||||||
/// Minecraft only sends a movement packet either after 20 ticks or if the
|
/// Minecraft only sends a movement packet either after 20 ticks or if the
|
||||||
/// player moved enough. This is that tick counter.
|
/// player moved enough. This is that tick counter.
|
||||||
|
@ -83,7 +83,6 @@ impl LocalPlayer {
|
||||||
|
|
||||||
packet_writer,
|
packet_writer,
|
||||||
|
|
||||||
physics_state: PhysicsState::default(),
|
|
||||||
client_information: ClientInformation::default(),
|
client_information: ClientInformation::default(),
|
||||||
players: HashMap::new(),
|
players: HashMap::new(),
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ use azalea_protocol::packets::game::{
|
||||||
serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket,
|
serverbound_move_player_rot_packet::ServerboundMovePlayerRotPacket,
|
||||||
serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket,
|
serverbound_move_player_status_only_packet::ServerboundMovePlayerStatusOnlyPacket,
|
||||||
};
|
};
|
||||||
use azalea_world::entity::MinecraftEntityId;
|
use azalea_world::entity::metadata::Sprinting;
|
||||||
|
use azalea_world::entity::{Attributes, MinecraftEntityId};
|
||||||
use azalea_world::{entity, MoveEntityError};
|
use azalea_world::{entity, MoveEntityError};
|
||||||
use bevy_ecs::system::Query;
|
use bevy_ecs::system::Query;
|
||||||
use std::backtrace::Backtrace;
|
use std::backtrace::Backtrace;
|
||||||
|
@ -39,7 +40,7 @@ impl Client {
|
||||||
/// recommended.
|
/// recommended.
|
||||||
pub fn set_jumping(&mut self, jumping: bool) {
|
pub fn set_jumping(&mut self, jumping: bool) {
|
||||||
let mut ecs = self.ecs.lock();
|
let mut ecs = self.ecs.lock();
|
||||||
let mut physics = self.query::<&mut entity::Physics>(&mut ecs).into_inner();
|
let mut physics = self.query::<&mut entity::Physics>(&mut ecs);
|
||||||
physics.jumping = jumping;
|
physics.jumping = jumping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +68,7 @@ pub(crate) fn send_position(
|
||||||
(
|
(
|
||||||
&MinecraftEntityId,
|
&MinecraftEntityId,
|
||||||
&mut LocalPlayer,
|
&mut LocalPlayer,
|
||||||
|
&mut PhysicsState,
|
||||||
&entity::Position,
|
&entity::Position,
|
||||||
&mut entity::LastSentPosition,
|
&mut entity::LastSentPosition,
|
||||||
&mut entity::Physics,
|
&mut entity::Physics,
|
||||||
|
@ -75,10 +77,17 @@ pub(crate) fn send_position(
|
||||||
&LocalPlayerInLoadedChunk,
|
&LocalPlayerInLoadedChunk,
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
for (id, mut local_player, position, mut last_sent_position, mut physics, sprinting) in
|
for (
|
||||||
query.iter_mut()
|
id,
|
||||||
|
mut local_player,
|
||||||
|
mut physics_state,
|
||||||
|
position,
|
||||||
|
mut last_sent_position,
|
||||||
|
mut physics,
|
||||||
|
sprinting,
|
||||||
|
) in query.iter_mut()
|
||||||
{
|
{
|
||||||
local_player.send_sprinting_if_needed(id, sprinting);
|
local_player.send_sprinting_if_needed(id, sprinting, &mut physics_state);
|
||||||
|
|
||||||
let packet = {
|
let packet = {
|
||||||
// TODO: the camera being able to be controlled by other entities isn't
|
// TODO: the camera being able to be controlled by other entities isn't
|
||||||
|
@ -90,13 +99,13 @@ pub(crate) fn send_position(
|
||||||
let y_rot_delta = (physics.y_rot - physics.y_rot_last) as f64;
|
let y_rot_delta = (physics.y_rot - physics.y_rot_last) as f64;
|
||||||
let x_rot_delta = (physics.x_rot - physics.x_rot_last) as f64;
|
let x_rot_delta = (physics.x_rot - physics.x_rot_last) as f64;
|
||||||
|
|
||||||
local_player.physics_state.position_remainder += 1;
|
physics_state.position_remainder += 1;
|
||||||
|
|
||||||
// boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) >
|
// boolean sendingPosition = Mth.lengthSquared(xDelta, yDelta, zDelta) >
|
||||||
// Mth.square(2.0E-4D) || this.positionReminder >= 20;
|
// Mth.square(2.0E-4D) || this.positionReminder >= 20;
|
||||||
let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
|
let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
|
||||||
> 2.0e-4f64.powi(2))
|
> 2.0e-4f64.powi(2))
|
||||||
|| local_player.physics_state.position_remainder >= 20;
|
|| physics_state.position_remainder >= 20;
|
||||||
let sending_rotation = y_rot_delta != 0.0 || x_rot_delta != 0.0;
|
let sending_rotation = y_rot_delta != 0.0 || x_rot_delta != 0.0;
|
||||||
|
|
||||||
// if self.is_passenger() {
|
// if self.is_passenger() {
|
||||||
|
@ -146,7 +155,7 @@ pub(crate) fn send_position(
|
||||||
|
|
||||||
if sending_position {
|
if sending_position {
|
||||||
**last_sent_position = **position;
|
**last_sent_position = **position;
|
||||||
local_player.physics_state.position_remainder = 0;
|
physics_state.position_remainder = 0;
|
||||||
}
|
}
|
||||||
if sending_rotation {
|
if sending_rotation {
|
||||||
physics.y_rot_last = physics.y_rot;
|
physics.y_rot_last = physics.y_rot;
|
||||||
|
@ -170,8 +179,9 @@ impl LocalPlayer {
|
||||||
&mut self,
|
&mut self,
|
||||||
id: &MinecraftEntityId,
|
id: &MinecraftEntityId,
|
||||||
sprinting: &entity::metadata::Sprinting,
|
sprinting: &entity::metadata::Sprinting,
|
||||||
|
physics_state: &mut PhysicsState,
|
||||||
) {
|
) {
|
||||||
let was_sprinting = self.physics_state.was_sprinting;
|
let was_sprinting = physics_state.was_sprinting;
|
||||||
if **sprinting != was_sprinting {
|
if **sprinting != was_sprinting {
|
||||||
let sprinting_action = if **sprinting {
|
let sprinting_action = if **sprinting {
|
||||||
azalea_protocol::packets::game::serverbound_player_command_packet::Action::StartSprinting
|
azalea_protocol::packets::game::serverbound_player_command_packet::Action::StartSprinting
|
||||||
|
@ -186,7 +196,7 @@ impl LocalPlayer {
|
||||||
}
|
}
|
||||||
.get(),
|
.get(),
|
||||||
);
|
);
|
||||||
self.physics_state.was_sprinting = **sprinting;
|
physics_state.was_sprinting = **sprinting;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,84 +234,6 @@ impl LocalPlayer {
|
||||||
physics_state.left_impulse *= multiplier;
|
physics_state.left_impulse *= multiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start walking in the given direction. To sprint, use
|
|
||||||
/// [`Client::sprint`]. To stop walking, call walk with
|
|
||||||
/// `WalkDirection::None`.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Walk for 1 second
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use azalea_client::{Client, WalkDirection};
|
|
||||||
/// # use std::time::Duration;
|
|
||||||
/// # async fn example(mut bot: Client) {
|
|
||||||
/// bot.walk(WalkDirection::Forward);
|
|
||||||
/// tokio::time::sleep(Duration::from_secs(1)).await;
|
|
||||||
/// bot.walk(WalkDirection::None);
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn walk(
|
|
||||||
direction: WalkDirection,
|
|
||||||
physics_state: &mut PhysicsState,
|
|
||||||
sprinting: &mut entity::metadata::Sprinting,
|
|
||||||
attributes: &mut entity::Attributes,
|
|
||||||
) {
|
|
||||||
physics_state.move_direction = direction;
|
|
||||||
|
|
||||||
Self::set_sprinting(false, sprinting, attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start sprinting in the given direction. To stop moving, call
|
|
||||||
/// [`Client::walk(WalkDirection::None)`]
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// Sprint for 1 second
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// # use azalea_client::{Client, WalkDirection, SprintDirection};
|
|
||||||
/// # use std::time::Duration;
|
|
||||||
/// # async fn example(mut bot: Client) {
|
|
||||||
/// bot.sprint(SprintDirection::Forward);
|
|
||||||
/// tokio::time::sleep(Duration::from_secs(1)).await;
|
|
||||||
/// bot.walk(WalkDirection::None);
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn sprint(&mut self, direction: SprintDirection, physics_state: &mut PhysicsState) {
|
|
||||||
physics_state.move_direction = WalkDirection::from(direction);
|
|
||||||
physics_state.trying_to_sprint = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change whether we're sprinting by adding an attribute modifier to the
|
|
||||||
/// player. You should use the [`walk`] and [`sprint`] methods instead.
|
|
||||||
/// Returns if the operation was successful.
|
|
||||||
fn set_sprinting(
|
|
||||||
sprinting: bool,
|
|
||||||
currently_sprinting: &mut entity::metadata::Sprinting,
|
|
||||||
attributes: &mut entity::Attributes,
|
|
||||||
) -> bool {
|
|
||||||
**currently_sprinting = sprinting;
|
|
||||||
if sprinting {
|
|
||||||
attributes
|
|
||||||
.speed
|
|
||||||
.insert(entity::attributes::sprinting_modifier())
|
|
||||||
.is_ok()
|
|
||||||
} else {
|
|
||||||
attributes
|
|
||||||
.speed
|
|
||||||
.remove(&entity::attributes::sprinting_modifier().uuid)
|
|
||||||
.is_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether the player is moving fast enough to be able to start sprinting.
|
|
||||||
fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
|
|
||||||
// if self.underwater() {
|
|
||||||
// self.has_forward_impulse()
|
|
||||||
// } else {
|
|
||||||
physics_state.forward_impulse > 0.8
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes the bot do one physics tick. Note that this is already handled
|
/// Makes the bot do one physics tick. Note that this is already handled
|
||||||
|
@ -310,6 +242,7 @@ pub fn local_player_ai_step(
|
||||||
mut query: Query<
|
mut query: Query<
|
||||||
(
|
(
|
||||||
&mut LocalPlayer,
|
&mut LocalPlayer,
|
||||||
|
&mut PhysicsState,
|
||||||
&mut entity::Physics,
|
&mut entity::Physics,
|
||||||
&mut entity::Position,
|
&mut entity::Position,
|
||||||
&mut entity::metadata::Sprinting,
|
&mut entity::metadata::Sprinting,
|
||||||
|
@ -319,12 +252,16 @@ pub fn local_player_ai_step(
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
println!("local_player_ai_step");
|
println!("local_player_ai_step");
|
||||||
for (mut local_player, mut physics, mut position, mut sprinting, mut attributes) in
|
for (
|
||||||
query.iter_mut()
|
local_player,
|
||||||
|
mut physics_state,
|
||||||
|
mut physics,
|
||||||
|
mut position,
|
||||||
|
mut sprinting,
|
||||||
|
mut attributes,
|
||||||
|
) in query.iter_mut()
|
||||||
{
|
{
|
||||||
let physics_state = &mut local_player.physics_state;
|
LocalPlayer::tick_controls(None, &mut physics_state);
|
||||||
|
|
||||||
LocalPlayer::tick_controls(None, physics_state);
|
|
||||||
|
|
||||||
// server ai step
|
// server ai step
|
||||||
physics.xxa = physics_state.left_impulse;
|
physics.xxa = physics_state.left_impulse;
|
||||||
|
@ -343,14 +280,14 @@ pub fn local_player_ai_step(
|
||||||
&& (
|
&& (
|
||||||
// !self.is_in_water()
|
// !self.is_in_water()
|
||||||
// || self.is_underwater() &&
|
// || self.is_underwater() &&
|
||||||
LocalPlayer::has_enough_impulse_to_start_sprinting(physics_state)
|
has_enough_impulse_to_start_sprinting(&physics_state)
|
||||||
&& has_enough_food_to_sprint
|
&& has_enough_food_to_sprint
|
||||||
// && !self.using_item()
|
// && !self.using_item()
|
||||||
// && !self.has_effect(MobEffects.BLINDNESS)
|
// && !self.has_effect(MobEffects.BLINDNESS)
|
||||||
&& trying_to_sprint
|
&& trying_to_sprint
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
LocalPlayer::set_sprinting(true, &mut sprinting, &mut attributes);
|
set_sprinting(true, &mut sprinting, &mut attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
azalea_physics::ai_step(
|
azalea_physics::ai_step(
|
||||||
|
@ -363,6 +300,107 @@ pub fn local_player_ai_step(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Start walking in the given direction. To sprint, use
|
||||||
|
/// [`Client::sprint`]. To stop walking, call walk with
|
||||||
|
/// `WalkDirection::None`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Walk for 1 second
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use azalea_client::{Client, WalkDirection};
|
||||||
|
/// # use std::time::Duration;
|
||||||
|
/// # async fn example(mut bot: Client) {
|
||||||
|
/// bot.walk(WalkDirection::Forward);
|
||||||
|
/// tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
/// bot.walk(WalkDirection::None);
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn walk(&mut self, direction: WalkDirection) {
|
||||||
|
let mut ecs = self.ecs.lock();
|
||||||
|
let (mut physics_state, mut sprinting, mut attributes) =
|
||||||
|
self.query::<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>(&mut ecs);
|
||||||
|
walk(
|
||||||
|
direction,
|
||||||
|
&mut physics_state,
|
||||||
|
&mut sprinting,
|
||||||
|
&mut attributes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start sprinting in the given direction. To stop moving, call
|
||||||
|
/// [`Client::walk(WalkDirection::None)`]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Sprint for 1 second
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use azalea_client::{Client, WalkDirection, SprintDirection};
|
||||||
|
/// # use std::time::Duration;
|
||||||
|
/// # async fn example(mut bot: Client) {
|
||||||
|
/// bot.sprint(SprintDirection::Forward);
|
||||||
|
/// tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
/// bot.walk(WalkDirection::None);
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn sprint(&mut self, direction: SprintDirection) {
|
||||||
|
let mut ecs = self.ecs.lock();
|
||||||
|
let mut physics_state = self.query::<&mut PhysicsState>(&mut ecs);
|
||||||
|
sprint(direction, &mut physics_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start walking in the given direction. To sprint, use
|
||||||
|
/// [`Client::sprint`]. To stop walking, call walk with
|
||||||
|
/// `WalkDirection::None`.
|
||||||
|
pub fn walk(
|
||||||
|
direction: WalkDirection,
|
||||||
|
physics_state: &mut PhysicsState,
|
||||||
|
sprinting: &mut entity::metadata::Sprinting,
|
||||||
|
attributes: &mut entity::Attributes,
|
||||||
|
) {
|
||||||
|
physics_state.move_direction = direction;
|
||||||
|
set_sprinting(false, sprinting, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start sprinting in the given direction.
|
||||||
|
pub fn sprint(direction: SprintDirection, physics_state: &mut PhysicsState) {
|
||||||
|
physics_state.move_direction = WalkDirection::from(direction);
|
||||||
|
physics_state.trying_to_sprint = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change whether we're sprinting by adding an attribute modifier to the
|
||||||
|
/// player. You should use the [`walk`] and [`sprint`] methods instead.
|
||||||
|
/// Returns if the operation was successful.
|
||||||
|
fn set_sprinting(
|
||||||
|
sprinting: bool,
|
||||||
|
currently_sprinting: &mut entity::metadata::Sprinting,
|
||||||
|
attributes: &mut entity::Attributes,
|
||||||
|
) -> bool {
|
||||||
|
**currently_sprinting = sprinting;
|
||||||
|
if sprinting {
|
||||||
|
attributes
|
||||||
|
.speed
|
||||||
|
.insert(entity::attributes::sprinting_modifier())
|
||||||
|
.is_ok()
|
||||||
|
} else {
|
||||||
|
attributes
|
||||||
|
.speed
|
||||||
|
.remove(&entity::attributes::sprinting_modifier().uuid)
|
||||||
|
.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether the player is moving fast enough to be able to start sprinting.
|
||||||
|
fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
|
||||||
|
// if self.underwater() {
|
||||||
|
// self.has_forward_impulse()
|
||||||
|
// } else {
|
||||||
|
physics_state.forward_impulse > 0.8
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub enum WalkDirection {
|
pub enum WalkDirection {
|
||||||
#[default]
|
#[default]
|
||||||
|
|
|
@ -61,7 +61,7 @@ fn handle_packets(
|
||||||
mut mut_local_player_query: Query<&mut LocalPlayer>,
|
mut mut_local_player_query: Query<&mut LocalPlayer>,
|
||||||
mut mut_health_query: Query<&mut Health>,
|
mut mut_health_query: Query<&mut Health>,
|
||||||
mut mut_position_query: Query<&mut Position>,
|
mut mut_position_query: Query<&mut Position>,
|
||||||
|
|
||||||
combat_kill_query: Query<(&MinecraftEntityId, Option<&Dead>)>,
|
combat_kill_query: Query<(&MinecraftEntityId, Option<&Dead>)>,
|
||||||
mut position_query: Query<(
|
mut position_query: Query<(
|
||||||
&mut LocalPlayer,
|
&mut LocalPlayer,
|
||||||
|
@ -279,7 +279,7 @@ fn handle_packets(
|
||||||
// we call a function instead of setting the fields ourself since the
|
// we call a function instead of setting the fields ourself since the
|
||||||
// function makes sure the rotations stay in their
|
// function makes sure the rotations stay in their
|
||||||
// ranges
|
// ranges
|
||||||
set_rotation(physics.into_inner(), y_rot, x_rot);
|
set_rotation(&mut physics, y_rot, x_rot);
|
||||||
// TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means
|
// TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means
|
||||||
// so investigate that ig
|
// so investigate that ig
|
||||||
let new_pos = Vec3 {
|
let new_pos = Vec3 {
|
||||||
|
|
|
@ -432,7 +432,7 @@ where
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
self.bot_datas.lock().clone().into_iter()
|
self.bot_datas.clone().lock().into_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue