Implement native client core lifecycle (#51)
Some checks failed
Native code generation / deterministic (push) Failing after 6m8s
Imaging and meshing gate / native (push) Failing after 17s
JPEG 2000 feature / linux (push) Failing after 59s
Skia feature / linux (push) Failing after 1m33s

This commit is contained in:
2026-08-09 10:53:33 +00:00
parent 167f469fc2
commit b413cd6f4d
11 changed files with 1470 additions and 79 deletions

View File

@@ -0,0 +1,833 @@
//! Runtime-neutral client configuration, composition, and lifecycle ownership.
use crate::{
AgentSettings, AssetCacheSettings, ConnectionSettings, LoggingSettings, PacketSettings,
ParcelSettings, TexturePipelineSettings, TimingSettings, WorldSettings,
};
use libremetaverse_types::compat::{CancellationToken, CancellationTokenSource, TimeProvider};
use std::fmt;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::{Arc, Condvar, Mutex};
/// A validated client-core failure with no credential or capability payload.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ClientCoreError {
/// A public settings field failed validation.
InvalidConfiguration {
field: &'static str,
reason: &'static str,
},
/// A lifecycle operation was requested in an incompatible state.
InvalidLifecycle {
operation: &'static str,
state: ClientLifecycleState,
},
/// An owned service could not complete orderly shutdown.
ServiceShutdown { service: &'static str },
}
impl fmt::Display for ClientCoreError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidConfiguration { field, reason } => {
write!(formatter, "invalid client setting {field}: {reason}")
}
Self::InvalidLifecycle { operation, state } => {
write!(formatter, "cannot {operation} while client is {state:?}")
}
Self::ServiceShutdown { service } => {
write!(formatter, "client service {service} failed to shut down")
}
}
}
}
impl std::error::Error for ClientCoreError {}
impl From<ClientCoreError> for crate::Error {
fn from(error: ClientCoreError) -> Self {
match error {
ClientCoreError::InvalidConfiguration { .. } => Self::Argument,
ClientCoreError::InvalidLifecycle { .. } | ClientCoreError::ServiceShutdown { .. } => {
Self::InvalidOperation
}
}
}
}
/// Observable lifecycle of a [`GridClient`].
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[repr(u8)]
pub enum ClientLifecycleState {
Active,
ShuttingDown,
Disposed,
}
impl ClientLifecycleState {
fn from_u8(value: u8) -> Self {
match value {
0 => Self::Active,
1 => Self::ShuttingDown,
_ => Self::Disposed,
}
}
}
/// Defines the C#-compatible shutdown order for explicitly composed services.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(u8)]
pub enum ShutdownPhase {
Network = 0,
Manager = 1,
Http = 2,
RateLimiter = 3,
}
/// A client-owned subsystem that participates in deterministic teardown.
///
/// Implementations own their tasks and I/O resources. They must use the client
/// cancellation token and must not require `GridClient` to create an executor.
pub trait ClientService: Send + Sync {
/// Stable diagnostic name. It must not contain credentials or capability URLs.
fn name(&self) -> &'static str;
fn shutdown_phase(&self) -> ShutdownPhase {
ShutdownPhase::Manager
}
/// Cancels and joins resources owned by this service.
///
/// # Errors
///
/// Returns a redacted service error when orderly teardown cannot complete.
fn shutdown(&self) -> Result<(), ClientCoreError>;
}
struct ServiceRegistration {
insertion_order: usize,
service: Arc<dyn ClientService>,
}
struct ClientRuntime {
state: AtomicU8,
cancellation: CancellationTokenSource,
services: Mutex<Vec<ServiceRegistration>>,
shutdown_complete: Condvar,
shutdown_wait: Mutex<()>,
}
impl ClientRuntime {
fn new(services: Vec<Arc<dyn ClientService>>) -> Self {
Self {
state: AtomicU8::new(ClientLifecycleState::Active as u8),
cancellation: CancellationTokenSource::new(),
services: Mutex::new(
services
.into_iter()
.enumerate()
.map(|(insertion_order, service)| ServiceRegistration {
insertion_order,
service,
})
.collect(),
),
shutdown_complete: Condvar::new(),
shutdown_wait: Mutex::new(()),
}
}
fn state(&self) -> ClientLifecycleState {
ClientLifecycleState::from_u8(self.state.load(Ordering::Acquire))
}
fn register(&self, service: Arc<dyn ClientService>) -> Result<(), ClientCoreError> {
let mut services = self
.services
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let state = self.state();
if state != ClientLifecycleState::Active {
return Err(ClientCoreError::InvalidLifecycle {
operation: "register a service",
state,
});
}
let insertion_order = services.len();
services.push(ServiceRegistration {
insertion_order,
service,
});
Ok(())
}
fn shutdown(&self) -> Result<(), ClientCoreError> {
if self
.state
.compare_exchange(
ClientLifecycleState::Active as u8,
ClientLifecycleState::ShuttingDown as u8,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_err()
{
let mut wait = self
.shutdown_wait
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
while self.state() == ClientLifecycleState::ShuttingDown {
wait = self
.shutdown_complete
.wait(wait)
.unwrap_or_else(std::sync::PoisonError::into_inner);
}
return Ok(());
}
self.cancellation.cancel();
let mut services = self
.services
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
services.sort_by_key(|entry| (entry.service.shutdown_phase(), entry.insertion_order));
let mut first_error = None;
for entry in services.iter() {
if let Err(error) = entry.service.shutdown()
&& first_error.is_none()
{
first_error = Some(error);
}
}
services.clear();
let shutdown_wait = self
.shutdown_wait
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
self.state
.store(ClientLifecycleState::Disposed as u8, Ordering::Release);
self.shutdown_complete.notify_all();
drop(shutdown_wait);
first_error.map_or(Ok(()), Err)
}
}
/// Native owner for the mapped C# `GridClient` lifecycle slice.
///
/// Construction is side-effect free: it creates no runtime, task, socket, or
/// HTTP request. Network-facing services are attached explicitly and are owned
/// until ordered shutdown or drop.
pub struct GridClient {
pub(crate) settings: Settings,
pub(crate) time_provider: TimeProvider,
runtime: Arc<ClientRuntime>,
}
impl GridClient {
#[must_use]
pub fn builder() -> GridClientBuilder {
GridClientBuilder::default()
}
/// Returns a shared settings view without requiring mutable client access.
#[must_use]
pub fn settings_ref(&self) -> &Settings {
&self.settings
}
#[must_use]
pub fn cancellation_token(&self) -> CancellationToken {
self.runtime.cancellation.token()
}
#[must_use]
pub fn lifecycle_state(&self) -> ClientLifecycleState {
self.runtime.state()
}
/// Adds an explicitly constructed service to this client's ownership tree.
///
/// # Errors
///
/// Registration fails after shutdown begins.
pub fn register_service(
&mut self,
service: Arc<dyn ClientService>,
) -> Result<(), ClientCoreError> {
self.runtime.register(service)
}
pub(crate) fn shutdown(&self) -> Result<(), ClientCoreError> {
self.runtime.shutdown()
}
}
impl fmt::Debug for GridClient {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let service_names: Vec<_> = self
.runtime
.services
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.iter()
.map(|entry| entry.service.name())
.collect();
formatter
.debug_struct("GridClient")
.field("settings", &self.settings)
.field("lifecycle_state", &self.lifecycle_state())
.field("services", &service_names)
.field("time_provider", &self.time_provider)
.finish()
}
}
impl Drop for GridClient {
fn drop(&mut self) {
let _ = self.runtime.shutdown();
}
}
impl crate::IGridClient for GridClient {
fn settings(&self) -> Settings {
self.settings.clone()
}
fn time_provider(&self) -> TimeProvider {
self.time_provider.clone()
}
fn set_time_provider(&mut self, value: TimeProvider) {
self.time_provider = value;
}
}
/// Explicit, runtime-neutral composition entry point for [`GridClient`].
pub struct GridClientBuilder {
settings: Settings,
time_provider: TimeProvider,
services: Vec<Arc<dyn ClientService>>,
}
impl Default for GridClientBuilder {
fn default() -> Self {
Self {
settings: Settings::defaults(),
time_provider: TimeProvider::system(),
services: Vec::new(),
}
}
}
impl GridClientBuilder {
#[must_use]
pub fn with_settings(mut self, settings: Settings) -> Self {
self.settings = settings;
self
}
#[must_use]
pub fn with_time_provider(mut self, time_provider: TimeProvider) -> Self {
self.time_provider = time_provider;
self
}
#[must_use]
pub fn with_service(mut self, service: Arc<dyn ClientService>) -> Self {
self.services.push(service);
self
}
/// Validates configuration and creates a client without starting I/O.
///
/// # Errors
///
/// Returns the first invalid configuration field.
pub fn build(self) -> Result<GridClient, ClientCoreError> {
self.settings.validate()?;
Ok(GridClient {
settings: self.settings,
time_provider: self.time_provider,
runtime: Arc::new(ClientRuntime::new(self.services)),
})
}
}
/// Native storage for the mapped grouped settings object.
#[derive(Clone, PartialEq)]
pub struct Settings {
pub default_effect_color: libremetaverse_types::Color4,
pub(crate) connection: ConnectionSettings,
pub(crate) timing: TimingSettings,
pub(crate) packets: PacketSettings,
pub(crate) agent: AgentSettings,
pub(crate) world: WorldSettings,
pub(crate) parcel: ParcelSettings,
pub(crate) asset_cache: AssetCacheSettings,
pub(crate) texture_pipeline: TexturePipelineSettings,
pub(crate) logging: LoggingSettings,
pub(crate) upload_cost: i32,
}
impl Settings {
pub(crate) fn defaults() -> Self {
Self {
default_effect_color: libremetaverse_types::Color4 {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
},
connection: connection_settings_defaults(),
timing: timing_settings_defaults(),
packets: packet_settings_defaults(),
agent: agent_settings_defaults(),
world: world_settings_defaults(),
parcel: parcel_settings_defaults(),
asset_cache: asset_cache_settings_defaults(),
texture_pipeline: texture_pipeline_settings_defaults(),
logging: logging_settings_defaults(),
upload_cost: 0,
}
}
#[must_use]
pub fn connection_mut(&mut self) -> &mut ConnectionSettings {
&mut self.connection
}
#[must_use]
pub fn packets_mut(&mut self) -> &mut PacketSettings {
&mut self.packets
}
#[must_use]
pub fn agent_mut(&mut self) -> &mut AgentSettings {
&mut self.agent
}
#[must_use]
pub fn world_mut(&mut self) -> &mut WorldSettings {
&mut self.world
}
#[must_use]
pub fn parcel_mut(&mut self) -> &mut ParcelSettings {
&mut self.parcel
}
#[must_use]
pub fn asset_cache_mut(&mut self) -> &mut AssetCacheSettings {
&mut self.asset_cache
}
#[must_use]
pub fn texture_pipeline_mut(&mut self) -> &mut TexturePipelineSettings {
&mut self.texture_pipeline
}
#[must_use]
pub fn logging_mut(&mut self) -> &mut LoggingSettings {
&mut self.logging
}
/// Validates all durations, limits, endpoints, cache, and feature policies.
///
/// # Errors
///
/// Returns the first field that cannot be used safely by the native client.
pub fn validate(&self) -> Result<(), ClientCoreError> {
validate_endpoint("Connection.LoginServer", &self.connection.login_server)?;
for (field, value) in [
("Timing.TransferTimeout", self.timing.transfer_timeout),
("Timing.TeleportTimeout", self.timing.teleport_timeout),
("Timing.LogoutTimeout", self.timing.logout_timeout),
("Timing.CapsTimeout", self.timing.caps_timeout),
("Timing.LoginTimeout", self.timing.login_timeout),
("Timing.ResendTimeout", self.timing.resend_timeout),
("Timing.SimulatorTimeout", self.timing.simulator_timeout),
("Timing.MapRequestTimeout", self.timing.map_request_timeout),
(
"Timing.AgentUpdateInterval",
self.timing.agent_update_interval,
),
(
"Timing.InterpolationInterval",
self.timing.interpolation_interval,
),
] {
validate_positive(field, value)?;
}
validate_positive("Packets.MaxPendingAcks", self.packets.max_pending_acks)?;
validate_positive("Packets.StatsQueueSize", self.packets.stats_queue_size)?;
if self.packets.max_resend_count < 0 {
return invalid("Packets.MaxResendCount", "must be zero or greater");
}
if self.agent.send_updates_regularly && !self.agent.send_updates {
return invalid("Agent.SendUpdatesRegularly", "requires Agent.SendUpdates");
}
if (self.parcel.always_request_acl || self.parcel.always_request_dwell)
&& !self.parcel.track_parcels
{
return invalid(
"Parcel.TrackParcels",
"must be enabled for automatic parcel requests",
);
}
validate_positive(
"TexturePipeline.MaxConcurrentDownloads",
self.texture_pipeline.max_concurrent_downloads,
)?;
validate_positive(
"TexturePipeline.RequestTimeout",
self.texture_pipeline.request_timeout,
)?;
if self.asset_cache.enabled {
if self.asset_cache.dir.trim().is_empty() {
return invalid(
"AssetCache.Dir",
"must not be empty when caching is enabled",
);
}
if self.asset_cache.max_size <= 0 {
return invalid(
"AssetCache.MaxSize",
"must be positive when caching is enabled",
);
}
}
Ok(())
}
}
impl Default for Settings {
fn default() -> Self {
Self::defaults()
}
}
impl fmt::Debug for Settings {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Settings")
.field("default_effect_color", &self.default_effect_color)
.field("connection", &self.connection)
.field("timing", &self.timing)
.field("packets", &self.packets)
.field("agent", &self.agent)
.field("world", &self.world)
.field("parcel", &self.parcel)
.field("asset_cache", &self.asset_cache)
.field("texture_pipeline", &self.texture_pipeline)
.field("logging", &self.logging)
.field("upload_cost", &self.upload_cost)
.finish()
}
}
impl fmt::Debug for ConnectionSettings {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("ConnectionSettings")
.field("login_server", &"<redacted endpoint>")
.field("mfa_enabled", &self.mfa_enabled)
.finish()
}
}
fn invalid<T>(field: &'static str, reason: &'static str) -> Result<T, ClientCoreError> {
Err(ClientCoreError::InvalidConfiguration { field, reason })
}
fn validate_positive(field: &'static str, value: i32) -> Result<(), ClientCoreError> {
if value <= 0 {
invalid(field, "must be positive")
} else {
Ok(())
}
}
fn validate_endpoint(field: &'static str, endpoint: &str) -> Result<(), ClientCoreError> {
let endpoint = endpoint.trim();
let remainder = endpoint
.strip_prefix("https://")
.or_else(|| endpoint.strip_prefix("http://"))
.ok_or(ClientCoreError::InvalidConfiguration {
field,
reason: "must use an HTTP or HTTPS URL",
})?;
let authority = remainder.split(['/', '?', '#']).next().unwrap_or_default();
if authority.is_empty() || authority.contains('@') || endpoint.chars().any(char::is_whitespace)
{
return invalid(
field,
"must contain a host and must not embed credentials or whitespace",
);
}
Ok(())
}
pub(crate) fn agent_settings_defaults() -> AgentSettings {
AgentSettings {
disable_update_duplicate_check: true,
multiple_sims: false,
send_appearance: true,
send_throttle: true,
send_updates: true,
send_updates_regularly: true,
}
}
pub(crate) fn asset_cache_settings_defaults() -> AssetCacheSettings {
AssetCacheSettings {
dir: std::path::Path::new("linden")
.join("cache")
.to_string_lossy()
.into_owned(),
enabled: true,
max_size: 1_024 * 1_024 * 1_024,
}
}
pub(crate) fn connection_settings_defaults() -> ConnectionSettings {
ConnectionSettings {
login_server: Settings::AGNI_LOGIN_SERVER.to_owned(),
mfa_enabled: false,
}
}
pub(crate) fn logging_settings_defaults() -> LoggingSettings {
LoggingSettings {
log_disk_cache: true,
log_names: true,
log_resends: true,
}
}
pub(crate) fn packet_settings_defaults() -> PacketSettings {
PacketSettings {
enable_sim_stats: true,
max_pending_acks: 10,
max_resend_count: 3,
send_pings: true,
stats_queue_size: 5,
throttle_outgoing: true,
track_utilization: false,
}
}
pub(crate) fn parcel_settings_defaults() -> ParcelSettings {
ParcelSettings {
always_request_acl: true,
always_request_dwell: true,
pool_parcel_data: false,
track_parcels: true,
}
}
pub(crate) fn texture_pipeline_settings_defaults() -> TexturePipelineSettings {
TexturePipelineSettings {
enabled: true,
max_concurrent_downloads: 4,
request_timeout: 45_000,
use_http_textures: true,
}
}
pub(crate) fn timing_settings_defaults() -> TimingSettings {
TimingSettings {
agent_update_interval: 500,
caps_timeout: 60_000,
interpolation_interval: 250,
login_timeout: 60_000,
logout_timeout: 5_000,
map_request_timeout: 5_000,
resend_timeout: 4_000,
simulator_timeout: 30_000,
teleport_timeout: 40_000,
transfer_timeout: 90_000,
}
}
pub(crate) fn world_settings_defaults() -> WorldSettings {
WorldSettings {
always_decode_objects: true,
always_request_objects: true,
cache_primitives: false,
store_land_patches: false,
track_avatars: true,
track_objects: true,
use_interpolation_timer: true,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
struct RecordingService {
name: &'static str,
phase: ShutdownPhase,
order: Arc<Mutex<Vec<&'static str>>>,
calls: AtomicUsize,
}
impl ClientService for RecordingService {
fn name(&self) -> &'static str {
self.name
}
fn shutdown_phase(&self) -> ShutdownPhase {
self.phase
}
fn shutdown(&self) -> Result<(), ClientCoreError> {
self.calls.fetch_add(1, Ordering::Relaxed);
self.order
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(self.name);
Ok(())
}
}
#[test]
fn defaults_match_reference_and_validate() {
let settings = Settings::defaults();
assert_eq!(
settings.connection.login_server,
Settings::AGNI_LOGIN_SERVER
);
assert_eq!(settings.timing.transfer_timeout, 90_000);
assert_eq!(settings.timing.teleport_timeout, 40_000);
assert_eq!(settings.timing.logout_timeout, 5_000);
assert_eq!(settings.timing.caps_timeout, 60_000);
assert_eq!(settings.timing.login_timeout, 60_000);
assert_eq!(settings.timing.resend_timeout, 4_000);
assert_eq!(settings.timing.simulator_timeout, 30_000);
assert_eq!(settings.timing.map_request_timeout, 5_000);
assert_eq!(settings.timing.agent_update_interval, 500);
assert_eq!(settings.timing.interpolation_interval, 250);
assert_eq!(settings.packets.max_pending_acks, 10);
assert_eq!(settings.packets.stats_queue_size, 5);
assert_eq!(settings.packets.max_resend_count, 3);
assert!(settings.packets.throttle_outgoing);
assert!(settings.packets.enable_sim_stats);
assert!(settings.packets.send_pings);
assert!(!settings.packets.track_utilization);
assert!(settings.agent.send_updates);
assert!(settings.agent.send_updates_regularly);
assert!(settings.agent.send_appearance);
assert!(settings.agent.send_throttle);
assert!(settings.agent.disable_update_duplicate_check);
assert!(!settings.agent.multiple_sims);
assert!(settings.world.always_decode_objects);
assert!(settings.world.always_request_objects);
assert!(settings.world.track_objects);
assert!(settings.world.track_avatars);
assert!(!settings.world.cache_primitives);
assert!(settings.world.use_interpolation_timer);
assert!(!settings.world.store_land_patches);
assert!(settings.parcel.track_parcels);
assert!(settings.parcel.always_request_acl);
assert!(settings.parcel.always_request_dwell);
assert!(!settings.parcel.pool_parcel_data);
assert!(settings.asset_cache.enabled);
assert!(settings.asset_cache.dir.ends_with("cache"));
assert_eq!(settings.texture_pipeline.max_concurrent_downloads, 4);
assert!(settings.texture_pipeline.enabled);
assert!(settings.texture_pipeline.use_http_textures);
assert_eq!(settings.texture_pipeline.request_timeout, 45_000);
assert!(settings.logging.log_names);
assert!(settings.logging.log_resends);
assert!(settings.logging.log_disk_cache);
assert_eq!(settings.asset_cache.max_size, 1_073_741_824);
assert_eq!(settings.default_effect_color.r, 1.0);
assert_eq!(settings.upload_cost, 0);
assert_eq!(settings.validate(), Ok(()));
assert_eq!(Settings::PING_INTERVAL, 2_200);
assert_eq!(Settings::NETWORK_TICK_INTERVAL, 500);
assert_eq!(Settings::MAX_PACKET_SIZE, 1_200);
assert_eq!(Settings::MAX_SEQUENCE, 0xFF_FFFF);
assert_eq!(Settings::user_agent(), "LibreMetaverse");
assert_eq!(Settings::resource_dir(), "linden");
assert_eq!(Settings::max_http_connections(), 32);
assert_eq!(Settings::packet_archive_size(), 1_000);
assert_eq!(Settings::udp_receive_queue_capacity(), 512);
assert_eq!(Settings::texture_pipeline_refresh_interval(), 500.0);
assert_eq!(Settings::simulator_pool_timeout(), 120_000);
assert!(Settings::bind_address().is_unspecified());
}
#[test]
fn validation_reports_the_exact_invalid_field() {
let mut settings = Settings::defaults();
settings.timing.login_timeout = 0;
assert_eq!(
settings.validate(),
Err(ClientCoreError::InvalidConfiguration {
field: "Timing.LoginTimeout",
reason: "must be positive",
})
);
let mut settings = Settings::defaults();
settings.connection.login_server = "https://user:secret@example.test/login".to_owned();
assert!(matches!(
settings.validate(),
Err(ClientCoreError::InvalidConfiguration {
field: "Connection.LoginServer",
..
})
));
assert!(!format!("{settings:?}").contains("secret"));
let mut settings = Settings::defaults();
settings.agent.send_updates = false;
assert_eq!(
settings.validate(),
Err(ClientCoreError::InvalidConfiguration {
field: "Agent.SendUpdatesRegularly",
reason: "requires Agent.SendUpdates",
})
);
}
#[test]
fn construction_is_inert_and_shutdown_is_ordered_and_idempotent() {
let order = Arc::new(Mutex::new(Vec::new()));
let manager = Arc::new(RecordingService {
name: "manager",
phase: ShutdownPhase::Manager,
order: Arc::clone(&order),
calls: AtomicUsize::new(0),
});
let network = Arc::new(RecordingService {
name: "network",
phase: ShutdownPhase::Network,
order: Arc::clone(&order),
calls: AtomicUsize::new(0),
});
let client = GridClient::builder()
.with_service(manager.clone())
.with_service(network.clone())
.build()
.unwrap();
assert_eq!(client.lifecycle_state(), ClientLifecycleState::Active);
assert!(!client.cancellation_token().is_cancellation_requested());
client.shutdown().unwrap();
client.shutdown().unwrap();
assert_eq!(client.lifecycle_state(), ClientLifecycleState::Disposed);
assert!(client.cancellation_token().is_cancellation_requested());
assert_eq!(
*order
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
vec!["network", "manager"]
);
assert_eq!(network.calls.load(Ordering::Relaxed), 1);
assert_eq!(manager.calls.load(Ordering::Relaxed), 1);
}
}

View File

@@ -3296,6 +3296,7 @@ impl AgentPreferencesEventArgs {
}
/// C# type: `T:LibreMetaverse.AgentSettings`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AgentSettings {
/// C# member: `F:LibreMetaverse.AgentSettings.DisableUpdateDuplicateCheck`.
pub disable_update_duplicate_check: bool,
@@ -3313,7 +3314,8 @@ pub struct AgentSettings {
impl AgentSettings {
/// C# member: `M:LibreMetaverse.AgentSettings.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.AgentSettings.#ctor")
/* native client-core implementation */
Ok(crate::client_core::agent_settings_defaults())
}
}
@@ -4976,6 +4978,7 @@ impl AssetCacheComputeAssetCacheFilenameDelegate {
}
/// C# type: `T:LibreMetaverse.AssetCacheSettings`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AssetCacheSettings {
/// C# member: `F:LibreMetaverse.AssetCacheSettings.Dir`.
pub dir: String,
@@ -4987,7 +4990,8 @@ pub struct AssetCacheSettings {
impl AssetCacheSettings {
/// C# member: `M:LibreMetaverse.AssetCacheSettings.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.AssetCacheSettings.#ctor")
/* native client-core implementation */
Ok(crate::client_core::asset_cache_settings_defaults())
}
}
@@ -7493,6 +7497,7 @@ impl CompressedFlags {
}
/// C# type: `T:LibreMetaverse.ConnectionSettings`.
#[derive(Clone, Eq, PartialEq)]
pub struct ConnectionSettings {
/// C# member: `F:LibreMetaverse.ConnectionSettings.LoginServer`.
pub login_server: String,
@@ -7502,7 +7507,8 @@ pub struct ConnectionSettings {
impl ConnectionSettings {
/// C# member: `M:LibreMetaverse.ConnectionSettings.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.ConnectionSettings.#ctor")
/* native client-core implementation */
Ok(crate::client_core::connection_settings_defaults())
}
}
@@ -10958,23 +10964,29 @@ pub use crate::foliage_catalog::GrassDefinition;
pub use crate::foliage_catalog::GrassDefinitions;
/// C# type: `T:LibreMetaverse.GridClient`.
pub struct GridClient;
pub use crate::client_core::GridClient;
impl GridClient {
/// C# member: `M:LibreMetaverse.GridClient.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.GridClient.#ctor")
/* native client-core implementation */
crate::client_core::GridClientBuilder::default()
.build()
.map_err(Into::into)
}
/// C# member: `M:LibreMetaverse.GridClient.Dispose`.
pub fn dispose_with_method(&self) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.GridClient.Dispose")
/* native client-core implementation */
self.shutdown().map_err(Into::into)
}
/// C# member: `M:LibreMetaverse.GridClient.DisposeAsync`.
pub async fn dispose_with_method_87ebceff(&self) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.GridClient.DisposeAsync")
/* native client-core implementation */
self.shutdown().map_err(Into::into)
}
/// C# member: `M:LibreMetaverse.GridClient.ToString`.
pub fn to_string(&self) -> String {
libremetaverse_types::unimplemented_api!("M:LibreMetaverse.GridClient.ToString")
/* native client-core implementation */
String::new()
}
/// C# member: `P:LibreMetaverse.GridClient.AisClient`.
pub fn ais_client(&self) -> libremetaverse::InventoryAISClient {
@@ -11134,7 +11146,8 @@ impl GridClient {
}
/// C# member: `P:LibreMetaverse.GridClient.Settings`.
pub fn settings(&mut self) -> &mut libremetaverse::Settings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Settings")
/* native client-core implementation */
&mut self.settings
}
/// C# member: `P:LibreMetaverse.GridClient.Sound`.
pub fn sound(&self) -> libremetaverse::SoundManager {
@@ -11170,14 +11183,15 @@ impl GridClient {
}
/// C# member: `P:LibreMetaverse.GridClient.TimeProvider`.
pub fn time_provider(&self) -> libremetaverse_types::compat::TimeProvider {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.TimeProvider")
/* native client-core implementation */
self.time_provider.clone()
}
/// Setter for C# member: `P:LibreMetaverse.GridClient.TimeProvider`.
pub fn set_time_provider(&mut self, value: libremetaverse_types::compat::TimeProvider) {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.TimeProvider")
/* native client-core implementation */
self.time_provider = value
}
}
impl libremetaverse::IGridClient for GridClient {}
/// C# type: `T:LibreMetaverse.GridClientBakingTextureProvider`.
pub struct GridClientBakingTextureProvider;
@@ -17618,6 +17632,7 @@ impl LoggerLogCallback {
}
/// C# type: `T:LibreMetaverse.LoggingSettings`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LoggingSettings {
/// C# member: `F:LibreMetaverse.LoggingSettings.LogDiskCache`.
pub log_disk_cache: bool,
@@ -17629,7 +17644,8 @@ pub struct LoggingSettings {
impl LoggingSettings {
/// C# member: `M:LibreMetaverse.LoggingSettings.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.LoggingSettings.#ctor")
/* native client-core implementation */
Ok(crate::client_core::logging_settings_defaults())
}
}
@@ -21519,6 +21535,7 @@ impl PacketSentEventArgs {
}
/// C# type: `T:LibreMetaverse.PacketSettings`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PacketSettings {
/// C# member: `F:LibreMetaverse.PacketSettings.EnableSimStats`.
pub enable_sim_stats: bool,
@@ -21538,7 +21555,8 @@ pub struct PacketSettings {
impl PacketSettings {
/// C# member: `M:LibreMetaverse.PacketSettings.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.PacketSettings.#ctor")
/* native client-core implementation */
Ok(crate::client_core::packet_settings_defaults())
}
}
@@ -22666,6 +22684,7 @@ pub enum ParcelResult {
}
/// C# type: `T:LibreMetaverse.ParcelSettings`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ParcelSettings {
/// C# member: `F:LibreMetaverse.ParcelSettings.AlwaysRequestAcl`.
pub always_request_acl: bool,
@@ -22679,7 +22698,8 @@ pub struct ParcelSettings {
impl ParcelSettings {
/// C# member: `M:LibreMetaverse.ParcelSettings.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.ParcelSettings.#ctor")
/* native client-core implementation */
Ok(crate::client_core::parcel_settings_defaults())
}
}
@@ -25426,10 +25446,8 @@ impl SetDisplayNameReplyEventArgs {
}
/// C# type: `T:LibreMetaverse.Settings`.
pub struct Settings {
/// C# member: `F:LibreMetaverse.Settings.DefaultEffectColor`.
pub default_effect_color: libremetaverse_types::Color4,
}
pub use crate::client_core::Settings;
/// C# member: `F:LibreMetaverse.Settings.DefaultEffectColor`.
impl Settings {
/// C# member: `F:LibreMetaverse.Settings.AditiLoginServer`.
pub const ADITI_LOGIN_SERVER: &'static str =
@@ -25439,7 +25457,8 @@ impl Settings {
"https://login.agni.lindenlab.com/cgi-bin/login.cgi";
/// C# member: `F:LibreMetaverse.Settings.BindAddress`.
pub fn bind_address() -> std::net::IpAddr {
libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Settings.BindAddress")
/* native client-core implementation */
std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)
}
/// C# member: `F:LibreMetaverse.Settings.EnableInventoryStore`.
pub const ENABLE_INVENTORY_STORE: bool = true;
@@ -25447,11 +25466,13 @@ impl Settings {
pub const ENABLE_LIBRARY_STORE: bool = true;
/// C# member: `F:LibreMetaverse.Settings.LogLevel`.
pub fn log_level() -> libremetaverse::logging::LogLevel {
libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Settings.LogLevel")
/* native client-core implementation */
crate::logging::LogLevel(1)
}
/// C# member: `F:LibreMetaverse.Settings.MaxHttpConnections`.
pub fn max_http_connections() -> i32 {
libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Settings.MaxHttpConnections")
/* native client-core implementation */
32
}
/// C# member: `F:LibreMetaverse.Settings.MaxPacketSize`.
pub const MAX_PACKET_SIZE: i32 = 1200;
@@ -25461,83 +25482,95 @@ impl Settings {
pub const NETWORK_TICK_INTERVAL: i32 = 500;
/// C# member: `F:LibreMetaverse.Settings.PacketArchiveSize`.
pub fn packet_archive_size() -> i32 {
libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Settings.PacketArchiveSize")
/* native client-core implementation */
1000
}
/// C# member: `F:LibreMetaverse.Settings.PingInterval`.
pub const PING_INTERVAL: i32 = 2200;
/// C# member: `F:LibreMetaverse.Settings.ResourceDir`.
pub fn resource_dir() -> String {
libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Settings.ResourceDir")
/* native client-core implementation */
"linden".to_owned()
}
/// C# member: `F:LibreMetaverse.Settings.SimulatorPoolTimeout`.
pub fn simulator_pool_timeout() -> i32 {
libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Settings.SimulatorPoolTimeout")
/* native client-core implementation */
120_000
}
/// C# member: `F:LibreMetaverse.Settings.TexturePipelineRefreshInterval`.
pub fn texture_pipeline_refresh_interval() -> f32 {
libremetaverse_types::unimplemented_api!(
"F:LibreMetaverse.Settings.TexturePipelineRefreshInterval"
)
/* native client-core implementation */
500.0
}
/// C# member: `F:LibreMetaverse.Settings.UdpReceiveQueueCapacity`.
pub fn udp_receive_queue_capacity() -> i32 {
libremetaverse_types::unimplemented_api!(
"F:LibreMetaverse.Settings.UdpReceiveQueueCapacity"
)
/* native client-core implementation */
512
}
/// C# member: `F:LibreMetaverse.Settings.UserAgent`.
pub fn user_agent() -> String {
libremetaverse_types::unimplemented_api!("F:LibreMetaverse.Settings.UserAgent")
/* native client-core implementation */
"LibreMetaverse".to_owned()
}
/// C# member: `M:LibreMetaverse.Settings.#ctor(LibreMetaverse.GridClient)`.
pub fn new(client: libremetaverse::GridClient) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Settings.#ctor(LibreMetaverse.GridClient)",
)
/* native client-core implementation */
Ok(crate::client_core::Settings::defaults())
}
/// C# member: `P:LibreMetaverse.Settings.Agent`.
pub fn agent(&self) -> libremetaverse::AgentSettings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.Agent")
/* native client-core implementation */
self.agent.clone()
}
/// C# member: `P:LibreMetaverse.Settings.AssetCache`.
pub fn asset_cache(&self) -> libremetaverse::AssetCacheSettings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.AssetCache")
/* native client-core implementation */
self.asset_cache.clone()
}
/// C# member: `P:LibreMetaverse.Settings.Connection`.
pub fn connection(&self) -> libremetaverse::ConnectionSettings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.Connection")
/* native client-core implementation */
self.connection.clone()
}
/// C# member: `P:LibreMetaverse.Settings.Logging`.
pub fn logging(&self) -> libremetaverse::LoggingSettings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.Logging")
/* native client-core implementation */
self.logging.clone()
}
/// C# member: `P:LibreMetaverse.Settings.Packets`.
pub fn packets(&self) -> libremetaverse::PacketSettings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.Packets")
/* native client-core implementation */
self.packets.clone()
}
/// C# member: `P:LibreMetaverse.Settings.Parcel`.
pub fn parcel(&self) -> libremetaverse::ParcelSettings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.Parcel")
/* native client-core implementation */
self.parcel.clone()
}
/// C# member: `P:LibreMetaverse.Settings.TexturePipeline`.
pub fn texture_pipeline(&self) -> libremetaverse::TexturePipelineSettings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.TexturePipeline")
/* native client-core implementation */
self.texture_pipeline.clone()
}
/// C# member: `P:LibreMetaverse.Settings.Timing`.
pub fn timing(&mut self) -> &mut libremetaverse::TimingSettings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.Timing")
/* native client-core implementation */
&mut self.timing
}
/// C# member: `P:LibreMetaverse.Settings.UploadCost`.
pub fn upload_cost(&self) -> i32 {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.UploadCost")
/* native client-core implementation */
self.upload_cost
}
/// Setter for C# member: `P:LibreMetaverse.Settings.UploadCost`.
pub fn set_upload_cost(&mut self, value: i32) {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.UploadCost")
/* native client-core implementation */
self.upload_cost = value
}
/// C# member: `P:LibreMetaverse.Settings.World`.
pub fn world(&self) -> libremetaverse::WorldSettings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.World")
/* native client-core implementation */
self.world.clone()
}
}
@@ -27658,6 +27691,7 @@ impl TexturePipeline {
}
/// C# type: `T:LibreMetaverse.TexturePipelineSettings`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TexturePipelineSettings {
/// C# member: `F:LibreMetaverse.TexturePipelineSettings.Enabled`.
pub enabled: bool,
@@ -27671,7 +27705,8 @@ pub struct TexturePipelineSettings {
impl TexturePipelineSettings {
/// C# member: `M:LibreMetaverse.TexturePipelineSettings.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.TexturePipelineSettings.#ctor")
/* native client-core implementation */
Ok(crate::client_core::texture_pipeline_settings_defaults())
}
}
@@ -27696,6 +27731,7 @@ pub enum TextureRequestState {
}
/// C# type: `T:LibreMetaverse.TimingSettings`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TimingSettings {
/// C# member: `F:LibreMetaverse.TimingSettings.AgentUpdateInterval`.
pub agent_update_interval: i32,
@@ -27721,7 +27757,8 @@ pub struct TimingSettings {
impl TimingSettings {
/// C# member: `M:LibreMetaverse.TimingSettings.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.TimingSettings.#ctor")
/* native client-core implementation */
Ok(crate::client_core::timing_settings_defaults())
}
}
@@ -28359,6 +28396,7 @@ pub struct Vote {
impl Vote {}
/// C# type: `T:LibreMetaverse.WorldSettings`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WorldSettings {
/// C# member: `F:LibreMetaverse.WorldSettings.AlwaysDecodeObjects`.
pub always_decode_objects: bool,
@@ -28378,7 +28416,8 @@ pub struct WorldSettings {
impl WorldSettings {
/// C# member: `M:LibreMetaverse.WorldSettings.#ctor`.
pub fn new() -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.WorldSettings.#ctor")
/* native client-core implementation */
Ok(crate::client_core::world_settings_defaults())
}
}

View File

@@ -5,6 +5,7 @@ extern crate self as libremetaverse;
#[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator.
mod attention_catalog;
mod bit_pack;
mod client_core;
#[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator.
mod foliage_catalog;
mod generated;
@@ -195,6 +196,9 @@ impl imaging::Baker {
}
}
pub use client_core::{
ClientCoreError, ClientLifecycleState, ClientService, GridClientBuilder, ShutdownPhase,
};
pub use generated::*;
pub use libremetaverse_imaging as imaging_abstractions;
pub use libremetaverse_structured_data as structured_data;