Implement TestClient world commands (#93)
Some checks failed
Native code generation / deterministic (push) Failing after 31s
Imaging and meshing gate / native (push) Successful in 5m30s
JPEG 2000 feature / linux (push) Successful in 2m47s
Native Rust workspace compile / compile (push) Successful in 22m56s
Skia feature / linux (push) Successful in 31m28s
Some checks failed
Native code generation / deterministic (push) Failing after 31s
Imaging and meshing gate / native (push) Successful in 5m30s
JPEG 2000 feature / linux (push) Successful in 2m47s
Native Rust workspace compile / compile (push) Successful in 22m56s
Skia feature / linux (push) Successful in 31m28s
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
//! Native `TestClient` shell, registry, and first-wave command groups.
|
||||
|
||||
mod inventory;
|
||||
mod world;
|
||||
|
||||
use crate::commands::TEST_CLIENT_COMMANDS;
|
||||
use clap::Parser;
|
||||
@@ -68,6 +69,52 @@ pub const IMPLEMENTED_TEST_CLIENT_COMMANDS: &[&str] = &[
|
||||
"UploadScriptCommand",
|
||||
"ViewNotecardCommand",
|
||||
"XferCommand",
|
||||
"DownloadTerrainCommand",
|
||||
"GetEstateCovenantCommand",
|
||||
"UploadRawTerrainCommand",
|
||||
"AgentLocationsCommand",
|
||||
"FindSimCommand",
|
||||
"GridLayerCommand",
|
||||
"GridMapCommand",
|
||||
"ParcelDetailsCommand",
|
||||
"ParcelInfoCommand",
|
||||
"ParcelPrimOwnersCommand",
|
||||
"ParcelSelectObjectsCommand",
|
||||
"SyntaxIdCommand",
|
||||
"WindCommand",
|
||||
"BackCommand",
|
||||
"CrossRegionCommand",
|
||||
"CrouchCommand",
|
||||
"FlyCommand",
|
||||
"FlyToCommand",
|
||||
"FollowCommand",
|
||||
"ForwardCommand",
|
||||
"GoHome",
|
||||
"GotoCommand",
|
||||
"GotoLandmark",
|
||||
"JumpCommand",
|
||||
"LeftCommand",
|
||||
"LocationCommand",
|
||||
"MoveToCommand",
|
||||
"RightCommand",
|
||||
"SetHome",
|
||||
"SitCommand",
|
||||
"SitOnCommand",
|
||||
"StandCommand",
|
||||
"TurnToCommand",
|
||||
"ChangePermsCommand",
|
||||
"DeRezObjectCommand",
|
||||
"DownloadTextureCommand",
|
||||
"ExportCommand",
|
||||
"ExportParticlesCommand",
|
||||
"FindObjectsCommand",
|
||||
"FindTextureCommand",
|
||||
"ImportCommand",
|
||||
"PrimCountCommand",
|
||||
"PrimInfoCommand",
|
||||
"PrimRegexCommand",
|
||||
"TexturesCommand",
|
||||
"TreeCommand",
|
||||
"EchoMasterCommand",
|
||||
"IMCommand",
|
||||
"IMGroupCommand",
|
||||
@@ -106,7 +153,7 @@ pub fn pending_test_client_commands() -> Vec<&'static str> {
|
||||
version,
|
||||
about = "Run the native LibreMetaverse multi-client command shell",
|
||||
long_about = None,
|
||||
after_help = "Owned commands include @, debug, echomaster, help, im, imgroup, load, login, logpacket, logout, md5, quit, say, setmaster, setmasterkey, shout, showeffects, sleep, waitforlogin, whisper, plus the inventory/assets and appearance groups. Run `help` or `help COMMAND` inside the shell for the complete command registry."
|
||||
after_help = "Owned commands include @, debug, echomaster, help, im, imgroup, load, login, logpacket, logout, md5, quit, say, setmaster, setmasterkey, shout, showeffects, sleep, waitforlogin, whisper, plus the inventory, appearance, asset, movement, object, parcel, estate, and grid groups. Run `help` or `help COMMAND` inside the shell for the complete command registry."
|
||||
)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
struct Cli {
|
||||
@@ -137,7 +184,7 @@ struct Cli {
|
||||
/// Permit commands from known members of joined group-chat sessions.
|
||||
#[arg(long)]
|
||||
groupcommands: bool,
|
||||
/// Preserve the upstream flag for later texture-aware command groups.
|
||||
/// Automatically fetch newly observed object textures into the asset cache.
|
||||
#[arg(long)]
|
||||
gettextures: bool,
|
||||
/// Run bounded terminal commands from a file before the interactive loop.
|
||||
@@ -160,6 +207,10 @@ struct Cli {
|
||||
/// Permit commands that spend or transfer L$; each command also requires confirmation.
|
||||
#[arg(long)]
|
||||
allow_spending: bool,
|
||||
|
||||
/// Permit estate-owner commands; mutations still require their other gates.
|
||||
#[arg(long)]
|
||||
allow_estate_actions: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -245,7 +296,7 @@ enum ClientEvent {
|
||||
type BackendFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
type InventoryFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
|
||||
|
||||
trait ClientBackend: Send + Sync + inventory::Backend {
|
||||
trait ClientBackend: Send + Sync + inventory::Backend + world::Backend {
|
||||
fn id(&self) -> UUID;
|
||||
fn name(&self) -> String;
|
||||
fn connected(&self) -> bool;
|
||||
@@ -280,6 +331,7 @@ struct LiveBackend {
|
||||
name: String,
|
||||
subscriptions: Mutex<Vec<Subscription>>,
|
||||
inventory_state: Mutex<inventory::State>,
|
||||
world_state: Mutex<world::State>,
|
||||
closed: AtomicBool,
|
||||
}
|
||||
|
||||
@@ -290,6 +342,7 @@ impl LiveBackend {
|
||||
events: mpsc::Sender<ClientEvent>,
|
||||
dropped: Arc<AtomicUsize>,
|
||||
account_policy: inventory::MutationPolicy,
|
||||
get_textures: bool,
|
||||
cancellation: CancellationToken,
|
||||
) -> Result<Arc<Self>, ProgramError> {
|
||||
let mut client = GridClient::new()
|
||||
@@ -339,6 +392,7 @@ impl LiveBackend {
|
||||
name,
|
||||
subscriptions: Mutex::new(Vec::new()),
|
||||
inventory_state: Mutex::new(inventory::State::new(account_policy)),
|
||||
world_state: Mutex::new(world::State::new(account_policy, get_textures)),
|
||||
closed: AtomicBool::new(false),
|
||||
});
|
||||
backend.install(&events, &dropped);
|
||||
@@ -457,6 +511,7 @@ impl LiveBackend {
|
||||
);
|
||||
},
|
||||
)));
|
||||
world::install_live(self, &mut subscriptions);
|
||||
*lock(&self.subscriptions) = subscriptions;
|
||||
}
|
||||
}
|
||||
@@ -624,6 +679,7 @@ struct FakeBackend {
|
||||
people: Arc<Mutex<HashMap<String, UUID>>>,
|
||||
group_members: Arc<Mutex<HashSet<UUID>>>,
|
||||
inventory_state: Mutex<inventory::State>,
|
||||
world_state: Mutex<world::State>,
|
||||
}
|
||||
|
||||
impl FakeBackend {
|
||||
@@ -633,6 +689,7 @@ impl FakeBackend {
|
||||
people: Arc<Mutex<HashMap<String, UUID>>>,
|
||||
group_members: Arc<Mutex<HashSet<UUID>>>,
|
||||
policy: inventory::MutationPolicy,
|
||||
get_textures: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
@@ -642,6 +699,7 @@ impl FakeBackend {
|
||||
people,
|
||||
group_members,
|
||||
inventory_state: Mutex::new(inventory::State::new(policy)),
|
||||
world_state: Mutex::new(world::State::new(policy, get_textures)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,6 +821,10 @@ enum CommandCategory {
|
||||
Appearance,
|
||||
Communication,
|
||||
Inventory,
|
||||
Movement,
|
||||
Objects,
|
||||
Parcel,
|
||||
Simulator,
|
||||
TestClient,
|
||||
Other,
|
||||
}
|
||||
@@ -776,6 +838,7 @@ enum CommandHandler {
|
||||
Im,
|
||||
ImGroup,
|
||||
Inventory(inventory::Command),
|
||||
World(world::Command),
|
||||
Load,
|
||||
Login,
|
||||
LogPacket,
|
||||
@@ -936,6 +999,7 @@ fn built_in_commands() -> HashMap<String, CommandDefinition> {
|
||||
.into_iter()
|
||||
.chain(communication_commands())
|
||||
.chain(inventory::commands())
|
||||
.chain(world::commands())
|
||||
.map(|(name, description, category, handler)| {
|
||||
(
|
||||
name.into(),
|
||||
@@ -1081,6 +1145,7 @@ struct ClientManager {
|
||||
default_master_key: UUID,
|
||||
default_group_commands: bool,
|
||||
default_mutation_policy: inventory::MutationPolicy,
|
||||
default_get_textures: bool,
|
||||
cancellation: CancellationTokenSource,
|
||||
output: Arc<ProgramOutput>,
|
||||
events_tx: mpsc::Sender<ClientEvent>,
|
||||
@@ -1103,6 +1168,7 @@ impl ClientManager {
|
||||
master_key: UUID,
|
||||
group_commands: bool,
|
||||
mutation_policy: inventory::MutationPolicy,
|
||||
get_textures: bool,
|
||||
output: Arc<ProgramOutput>,
|
||||
) -> Self {
|
||||
let (events_tx, events_rx) = mpsc::channel(EVENT_QUEUE_CAPACITY);
|
||||
@@ -1119,6 +1185,7 @@ impl ClientManager {
|
||||
default_master_key: master_key,
|
||||
default_group_commands: group_commands,
|
||||
default_mutation_policy: mutation_policy,
|
||||
default_get_textures: get_textures,
|
||||
cancellation: CancellationTokenSource::new(),
|
||||
output,
|
||||
events_tx,
|
||||
@@ -1179,6 +1246,7 @@ impl ClientManager {
|
||||
Arc::clone(&self.fake_people),
|
||||
Arc::clone(&self.fake_group_members),
|
||||
self.default_mutation_policy,
|
||||
self.default_get_textures,
|
||||
));
|
||||
self.add_client(ManagedClient {
|
||||
backend,
|
||||
@@ -1206,6 +1274,7 @@ impl ClientManager {
|
||||
self.events_tx.clone(),
|
||||
Arc::clone(&self.dropped_events),
|
||||
self.default_mutation_policy,
|
||||
self.default_get_textures,
|
||||
self.cancellation.token(),
|
||||
)
|
||||
.await
|
||||
@@ -1220,6 +1289,7 @@ impl ClientManager {
|
||||
Arc::clone(&self.fake_people),
|
||||
Arc::clone(&self.fake_group_members),
|
||||
self.default_mutation_policy,
|
||||
self.default_get_textures,
|
||||
))),
|
||||
Err(_) => Err(ProgramError::Client(
|
||||
"could not make fake client UUID".into(),
|
||||
@@ -1710,6 +1780,9 @@ async fn execute_client_command(
|
||||
CommandHandler::Inventory(command) => {
|
||||
inventory::execute(client.backend.as_ref(), *command, args, from, cancellation).await
|
||||
}
|
||||
CommandHandler::World(command) => {
|
||||
world::execute(client.backend.as_ref(), *command, args, cancellation).await
|
||||
}
|
||||
CommandHandler::Md5 => {
|
||||
if args.len() != 1 {
|
||||
return "Usage: md5 [password]".into();
|
||||
@@ -1955,8 +2028,11 @@ pub fn main_entry() -> ExitCode {
|
||||
}
|
||||
|
||||
async fn run(cli: Cli) -> Result<(), ProgramError> {
|
||||
let mutation_policy =
|
||||
inventory::MutationPolicy::new(cli.allow_live_mutations, cli.allow_spending);
|
||||
let mutation_policy = inventory::MutationPolicy::new(
|
||||
cli.allow_live_mutations,
|
||||
cli.allow_spending,
|
||||
cli.allow_estate_actions,
|
||||
);
|
||||
let master_key = match cli.masterkey.as_deref() {
|
||||
Some(value) => UUID::new_with_string(value.into())
|
||||
.map_err(|_| ProgramError::Usage("--masterkey must be a UUID"))?,
|
||||
@@ -1978,6 +2054,7 @@ async fn run(cli: Cli) -> Result<(), ProgramError> {
|
||||
master_key,
|
||||
cli.groupcommands,
|
||||
mutation_policy,
|
||||
cli.gettextures,
|
||||
Arc::clone(&output),
|
||||
);
|
||||
run_fake_script(&mut manager, path).await?;
|
||||
@@ -2011,6 +2088,7 @@ async fn run(cli: Cli) -> Result<(), ProgramError> {
|
||||
master_key,
|
||||
cli.groupcommands,
|
||||
mutation_policy,
|
||||
cli.gettextures,
|
||||
Arc::clone(&output),
|
||||
);
|
||||
for account in accounts {
|
||||
@@ -2285,6 +2363,10 @@ async fn run_fake_event_directive(
|
||||
result.map_err(|reason| ProgramError::InvalidInput { line, reason })?;
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(result) = world::apply_fake_directive(manager, fields) {
|
||||
result.map_err(|reason| ProgramError::InvalidInput { line, reason })?;
|
||||
return Ok(());
|
||||
}
|
||||
match fields {
|
||||
["chat", client, source, name, message] => {
|
||||
let event = ClientEvent::Chat {
|
||||
|
||||
@@ -222,18 +222,23 @@ pub(super) struct MutationPolicy(u8);
|
||||
impl MutationPolicy {
|
||||
const MUTATIONS: u8 = 1;
|
||||
const SPENDING: u8 = 1 << 1;
|
||||
const ESTATE: u8 = 1 << 2;
|
||||
|
||||
pub(super) const fn new(mutations: bool, spending: bool) -> Self {
|
||||
Self((mutations as u8) | ((spending as u8) << 1))
|
||||
pub(super) const fn new(mutations: bool, spending: bool, estate: bool) -> Self {
|
||||
Self((mutations as u8) | ((spending as u8) << 1) | ((estate as u8) << 2))
|
||||
}
|
||||
|
||||
const fn mutations(self) -> bool {
|
||||
pub(super) const fn mutations(self) -> bool {
|
||||
self.0 & Self::MUTATIONS != 0
|
||||
}
|
||||
|
||||
const fn spending(self) -> bool {
|
||||
pub(super) const fn spending(self) -> bool {
|
||||
self.0 & Self::SPENDING != 0
|
||||
}
|
||||
|
||||
pub(super) const fn estate(self) -> bool {
|
||||
self.0 & Self::ESTATE != 0
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct State {
|
||||
@@ -1662,7 +1667,7 @@ fn decode_tga(bytes: &[u8]) -> Result<ManagedImage, String> {
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
fn encode_tga(image: &ManagedImage) -> Result<Vec<u8>, String> {
|
||||
pub(super) fn encode_tga(image: &ManagedImage) -> Result<Vec<u8>, String> {
|
||||
image
|
||||
.validate()
|
||||
.map_err(|_| "Invalid decoded image layout")?;
|
||||
|
||||
3661
programs/src/test_client/world.rs
Normal file
3661
programs/src/test_client/world.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user