Fix varregion scene capture and legacy rendering
This commit is contained in:
39
crates/libremetaverse/CHANGES.md
Normal file
39
crates/libremetaverse/CHANGES.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# MetaCrate compatibility deviations
|
||||
|
||||
This file records intentional differences from the C# LibreMetaverse source. Preserve or
|
||||
re-evaluate these changes when importing a newer upstream version.
|
||||
|
||||
## OpenSim varregion dimensions
|
||||
|
||||
- `GridRegion` retains the optional `MapBlockReply.Size` dimensions. Missing or zero dimensions
|
||||
fall back to 256 x 256 metres.
|
||||
- `Simulator::region_size()` exposes dimensions resolved after login and is marked `must_use`,
|
||||
while the mapped public `size_x` and `size_y` fields remain unchanged for API compatibility.
|
||||
- A matching map-region event updates the current simulator's resolved dimensions. Movement,
|
||||
border prediction, terrain lookup, and terrain archive export use the resolved dimensions.
|
||||
- The cache distinguishes explicit `Size` blocks from the 256 x 256 fallback. Startup waits for
|
||||
an explicit size until timeout, and a later fallback reply cannot downgrade a resolved size.
|
||||
- Region lookup no longer synthesizes the current simulator as a 256 x 256 `GridRegion`; it
|
||||
requests the authoritative map block.
|
||||
|
||||
Files: `src/grid_manager.rs`, `src/network_manager.rs`, `src/agent_movement.rs`,
|
||||
`src/asset_archive.rs`.
|
||||
|
||||
## Asynchronous region-reply correlation
|
||||
|
||||
- Name and handle lookups subscribe to the continuously dispatched `GridRegion` event stream
|
||||
before sending a request.
|
||||
- Each lookup accepts only the matching name or handle. Unrelated grid, object, avatar, or map
|
||||
traffic cannot complete the request early.
|
||||
- Cancellation and the mapped map-request timeout still bound the one-shot waiter.
|
||||
- The default map-request timeout is 15 seconds rather than 5 seconds because OpenSim map
|
||||
replies on the live varregion can arrive after 7-8 seconds during login.
|
||||
|
||||
Files: `src/grid_manager.rs`, `src/client_core.rs`.
|
||||
|
||||
## OpenSim asset transport fallback
|
||||
|
||||
- When the OpenSim asset capability returns no payload for an asset, the request falls back to
|
||||
the existing UDP asset transfer path instead of reporting a missing asset immediately.
|
||||
|
||||
File: `src/asset_manager.rs`.
|
||||
@@ -1768,13 +1768,14 @@ impl AgentMovementRuntime {
|
||||
if velocity == Vector3::zero() {
|
||||
return;
|
||||
}
|
||||
let (region_size_x, region_size_y) = simulator.region_size();
|
||||
let west = if velocity.x < -0.1 {
|
||||
-position.x / velocity.x
|
||||
} else {
|
||||
f32::MAX
|
||||
};
|
||||
let east = if velocity.x > 0.1 {
|
||||
(simulator.size_x as f32 - position.x) / velocity.x
|
||||
(region_size_x as f32 - position.x) / velocity.x
|
||||
} else {
|
||||
f32::MAX
|
||||
};
|
||||
@@ -1784,7 +1785,7 @@ impl AgentMovementRuntime {
|
||||
f32::MAX
|
||||
};
|
||||
let north = if velocity.y > 0.1 {
|
||||
(simulator.size_y as f32 - position.y) / velocity.y
|
||||
(region_size_y as f32 - position.y) / velocity.y
|
||||
} else {
|
||||
f32::MAX
|
||||
};
|
||||
@@ -1822,8 +1823,9 @@ impl AgentMovementRuntime {
|
||||
}
|
||||
let x = u32::try_from(simulator.handle >> 32).unwrap_or_default();
|
||||
let y = u32::try_from(simulator.handle & u64::from(u32::MAX)).unwrap_or_default();
|
||||
let size_x = simulator.size_x as f32;
|
||||
let size_y = simulator.size_y as f32;
|
||||
let (size_x, size_y) = simulator.region_size();
|
||||
let size_x = size_x as f32;
|
||||
let size_y = size_y as f32;
|
||||
if velocity.x < -0.5 && position.x < 128.0 {
|
||||
self.establish_child(x.wrapping_sub(256), y, BorderCrossingDirection::West);
|
||||
} else if velocity.x > 0.5 && position.x > size_x - 128.0 {
|
||||
|
||||
@@ -1385,8 +1385,9 @@ pub(crate) fn save_terrain(sim: crate::Simulator, terrain_path: String) -> Resul
|
||||
let name = sim.name.clone();
|
||||
let safe_name = name.replace(['/', '\\'], "_");
|
||||
let mut file = File::create(root.join(format!("{safe_name}.r32"))).map_err(io_error)?;
|
||||
for y in 0..sim.size_y {
|
||||
for x in 0..sim.size_x {
|
||||
let (size_x, size_y) = sim.region_size();
|
||||
for y in 0..size_y {
|
||||
for x in 0..size_x {
|
||||
let mut height = 0.0;
|
||||
if !sim.terrain_height_at_point(x as i32, y as i32, &mut height)? {
|
||||
height = 0.0;
|
||||
|
||||
@@ -795,7 +795,22 @@ impl AssetManager {
|
||||
&format!("{}_id", self.asset_type_to_string(type_)?),
|
||||
asset_id,
|
||||
);
|
||||
self.fetch(uri, None, token.clone()).await?
|
||||
if let Some(bytes) = self.fetch(uri, None, token.clone()).await? {
|
||||
Some(bytes)
|
||||
} else {
|
||||
self.request_asset_udp(
|
||||
asset_id,
|
||||
UUID::zero(),
|
||||
UUID::zero(),
|
||||
UUID::zero(),
|
||||
type_,
|
||||
priority,
|
||||
source_type,
|
||||
UUID::random()?,
|
||||
token.clone(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
} else {
|
||||
self.request_asset_udp(
|
||||
asset_id,
|
||||
|
||||
@@ -1722,7 +1722,7 @@ pub(crate) fn timing_settings_defaults() -> TimingSettings {
|
||||
interpolation_interval: 250,
|
||||
login_timeout: 60_000,
|
||||
logout_timeout: 5_000,
|
||||
map_request_timeout: 5_000,
|
||||
map_request_timeout: 15_000,
|
||||
resend_timeout: 4_000,
|
||||
simulator_timeout: 30_000,
|
||||
teleport_timeout: 40_000,
|
||||
@@ -1823,7 +1823,7 @@ mod tests {
|
||||
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.map_request_timeout, 15_000);
|
||||
assert_eq!(settings.timing.agent_update_interval, 500);
|
||||
assert_eq!(settings.timing.interpolation_interval, 250);
|
||||
assert_eq!(settings.packets.max_pending_acks, 10);
|
||||
|
||||
@@ -71,6 +71,8 @@ pub struct GridRegion {
|
||||
pub name: String,
|
||||
pub region_flags: RegionFlags,
|
||||
pub region_handle: u64,
|
||||
pub size_x: u32,
|
||||
pub size_y: u32,
|
||||
pub water_height: u8,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
@@ -367,6 +369,7 @@ struct GridEvents {
|
||||
struct CachedRegion {
|
||||
region: GridRegion,
|
||||
seen: SystemTime,
|
||||
explicit_size: bool,
|
||||
}
|
||||
pub(crate) struct GridManagerInner {
|
||||
client: Arc<GridClient>,
|
||||
@@ -664,39 +667,38 @@ impl GridManager {
|
||||
layer: GridLayerType,
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
) -> Result<Option<Option<GridRegion>>, Error> {
|
||||
let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token());
|
||||
token.throw_if_cancellation_requested()?;
|
||||
let key = name.trim().to_ascii_lowercase();
|
||||
if key.is_empty() {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
if let Some(region) = self.inner.region_by_name(&key) {
|
||||
return Ok(Some(Some(region)));
|
||||
}
|
||||
if let Some(simulator) = self
|
||||
if let Some(region) = self
|
||||
.inner
|
||||
.client
|
||||
.network()
|
||||
.current_sim()
|
||||
.filter(|simulator| simulator.name.eq_ignore_ascii_case(&key))
|
||||
.region_by_name(&key)
|
||||
.filter(|region| self.inner.region_size_is_explicit(region.region_handle))
|
||||
{
|
||||
return Ok(Some(Some(GridRegion {
|
||||
access: simulator.access,
|
||||
agents: 0,
|
||||
map_image_id: UUID::zero(),
|
||||
name: simulator.name.clone(),
|
||||
region_flags: simulator.flags,
|
||||
region_handle: simulator.handle,
|
||||
water_height: simulator.water_height.clamp(0.0, f32::from(u8::MAX)) as u8,
|
||||
x: ((simulator.handle >> 32) / 256) as i32,
|
||||
y: ((simulator.handle & u64::from(u32::MAX)) / 256) as i32,
|
||||
})));
|
||||
}
|
||||
let notified = self.inner.notify.notified();
|
||||
self.request_map_region(name, layer)?;
|
||||
if let Some(region) = self.inner.region_by_name(&key) {
|
||||
return Ok(Some(Some(region)));
|
||||
}
|
||||
self.wait_for(cancellation_token, notified).await?;
|
||||
Ok(Some(self.inner.region_by_name(&key)))
|
||||
let event_key = key.clone();
|
||||
let inner = Arc::clone(&self.inner);
|
||||
let (_subscription, receiver) = self.region_reply_receiver(move |region| {
|
||||
region.name.eq_ignore_ascii_case(&event_key)
|
||||
&& inner.region_size_is_explicit(region.region_handle)
|
||||
});
|
||||
self.request_map_region(name, layer)?;
|
||||
if let Some(region) = self
|
||||
.inner
|
||||
.region_by_name(&key)
|
||||
.filter(|region| self.inner.region_size_is_explicit(region.region_handle))
|
||||
{
|
||||
return Ok(Some(Some(region)));
|
||||
}
|
||||
let region = self
|
||||
.wait_for_region_reply(receiver, token)
|
||||
.await?
|
||||
.or_else(|| self.inner.region_by_name(&key));
|
||||
Ok(Some(region))
|
||||
}
|
||||
pub async fn get_grid_region_with_u_int64_grid_layer_type_cancellation_token(
|
||||
&self,
|
||||
@@ -704,18 +706,30 @@ impl GridManager {
|
||||
layer: GridLayerType,
|
||||
cancellation_token: Option<CancellationToken>,
|
||||
) -> Result<Option<Option<GridRegion>>, Error> {
|
||||
if let Some(region) = self.inner.region_by_handle(handle) {
|
||||
let token = cancellation_token.unwrap_or_else(|| self.inner.client.cancellation_token());
|
||||
token.throw_if_cancellation_requested()?;
|
||||
if let Some(region) = self
|
||||
.inner
|
||||
.region_by_handle(handle)
|
||||
.filter(|_| self.inner.region_size_is_explicit(handle))
|
||||
{
|
||||
return Ok(Some(Some(region)));
|
||||
}
|
||||
let x = u16::try_from((handle >> 32) / 256).map_err(|_| Error::Argument)?;
|
||||
let y = u16::try_from((handle & u64::from(u32::MAX)) / 256).map_err(|_| Error::Argument)?;
|
||||
let notified = self.inner.notify.notified();
|
||||
let inner = Arc::clone(&self.inner);
|
||||
let (_subscription, receiver) = self.region_reply_receiver(move |region| {
|
||||
region.region_handle == handle && inner.region_size_is_explicit(handle)
|
||||
});
|
||||
self.request_map_blocks(layer, x, y, x, y, true)?;
|
||||
if let Some(region) = self.inner.region_by_handle(handle) {
|
||||
return Ok(Some(Some(region)));
|
||||
}
|
||||
self.wait_for(cancellation_token, notified).await?;
|
||||
Ok(Some(self.inner.region_by_handle(handle)))
|
||||
let region = self
|
||||
.wait_for_region_reply(receiver, token)
|
||||
.await?
|
||||
.or_else(|| self.inner.region_by_handle(handle));
|
||||
Ok(Some(region))
|
||||
}
|
||||
pub async fn map_items(
|
||||
&self,
|
||||
@@ -751,6 +765,40 @@ impl GridManager {
|
||||
.max(1) as u64;
|
||||
tokio::select! { () = notified => Ok(()), () = token.cancelled() => Err(Error::Cancelled), () = tokio::time::sleep(Duration::from_millis(millis)) => Ok(()) }
|
||||
}
|
||||
fn region_reply_receiver(
|
||||
&self,
|
||||
matches: impl Fn(&GridRegion) -> bool + Send + Sync + 'static,
|
||||
) -> (Subscription, oneshot::Receiver<GridRegion>) {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let sender = Arc::new(Mutex::new(Some(sender)));
|
||||
let callback = Arc::clone(&sender);
|
||||
let subscription = self.subscribe_grid_region(Arc::new(move |event| {
|
||||
if matches(&event.region)
|
||||
&& let Some(sender) = mutex(&callback).take()
|
||||
{
|
||||
let _ = sender.send(event.region);
|
||||
}
|
||||
}));
|
||||
(subscription, receiver)
|
||||
}
|
||||
async fn wait_for_region_reply(
|
||||
&self,
|
||||
receiver: oneshot::Receiver<GridRegion>,
|
||||
token: CancellationToken,
|
||||
) -> Result<Option<GridRegion>, Error> {
|
||||
let millis = self
|
||||
.inner
|
||||
.client
|
||||
.settings_ref()
|
||||
.timing
|
||||
.map_request_timeout
|
||||
.max(1) as u64;
|
||||
tokio::select! {
|
||||
region = receiver => Ok(region.ok()),
|
||||
() = token.cancelled() => Err(Error::Cancelled),
|
||||
() = tokio::time::sleep(Duration::from_millis(millis)) => Ok(None),
|
||||
}
|
||||
}
|
||||
pub fn regions_read_only(&self) -> HashMap<String, GridRegion> {
|
||||
self.inner.prune();
|
||||
read(&self.inner.regions)
|
||||
@@ -814,10 +862,35 @@ impl GridManagerInner {
|
||||
.get(&handle)
|
||||
.map(|value| value.region.clone())
|
||||
}
|
||||
fn insert_region(&self, region: GridRegion) {
|
||||
fn region_size_is_explicit(&self, handle: u64) -> bool {
|
||||
self.prune();
|
||||
read(&self.handles)
|
||||
.get(&handle)
|
||||
.is_some_and(|value| value.explicit_size)
|
||||
}
|
||||
fn insert_region(&self, mut region: GridRegion, mut explicit_size: bool) {
|
||||
if !explicit_size
|
||||
&& let Some(cached) = read(&self.handles)
|
||||
.get(®ion.region_handle)
|
||||
.filter(|cached| cached.explicit_size)
|
||||
.cloned()
|
||||
{
|
||||
region.size_x = cached.region.size_x;
|
||||
region.size_y = cached.region.size_y;
|
||||
explicit_size = true;
|
||||
}
|
||||
if let Some(simulator) = self
|
||||
.client
|
||||
.network()
|
||||
.native_current_sim()
|
||||
.filter(|simulator| simulator.handle == region.region_handle)
|
||||
{
|
||||
simulator.native_set_region_size(region.size_x, region.size_y);
|
||||
}
|
||||
let cached = CachedRegion {
|
||||
region: region.clone(),
|
||||
seen: self.client.time_provider.get_utc_now(),
|
||||
explicit_size,
|
||||
};
|
||||
write(&self.regions).insert(region.name.to_ascii_lowercase(), cached.clone());
|
||||
write(&self.handles).insert(region.region_handle, cached);
|
||||
@@ -834,20 +907,27 @@ impl GridManagerInner {
|
||||
if let Ok(packet) =
|
||||
crate::packets::MapBlockReplyPacket::new_from_bytes(&event.data, &mut pos)
|
||||
{
|
||||
for block in packet.data.into_iter().take(65_535) {
|
||||
for (index, block) in packet.data.into_iter().take(65_535).enumerate() {
|
||||
let global_x = u32::from(block.x) * 256;
|
||||
let global_y = u32::from(block.y) * 256;
|
||||
self.insert_region(GridRegion {
|
||||
access: SimAccess(block.access),
|
||||
agents: block.agents,
|
||||
map_image_id: block.map_image_id,
|
||||
name: wire_string(&block.name),
|
||||
region_flags: RegionFlags(u64::from(block.region_flags)),
|
||||
region_handle: (u64::from(global_x) << 32) | u64::from(global_y),
|
||||
water_height: block.water_height,
|
||||
x: i32::from(block.x),
|
||||
y: i32::from(block.y),
|
||||
});
|
||||
let (size_x, size_y, explicit_size) =
|
||||
map_block_size(packet.size.get(index));
|
||||
self.insert_region(
|
||||
GridRegion {
|
||||
access: SimAccess(block.access),
|
||||
agents: block.agents,
|
||||
map_image_id: block.map_image_id,
|
||||
name: wire_string(&block.name),
|
||||
region_flags: RegionFlags(u64::from(block.region_flags)),
|
||||
region_handle: (u64::from(global_x) << 32) | u64::from(global_y),
|
||||
size_x,
|
||||
size_y,
|
||||
water_height: block.water_height,
|
||||
x: i32::from(block.x),
|
||||
y: i32::from(block.y),
|
||||
},
|
||||
explicit_size,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -953,6 +1033,18 @@ impl GridManagerInner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_block_size(size: Option<&crate::packets::MapBlockReplyPacketSizeBlock>) -> (u32, u32, bool) {
|
||||
size.filter(|size| size.size_x != 0 && size.size_y != 0)
|
||||
.map_or(
|
||||
(
|
||||
Simulator::DEFAULT_REGION_SIZE_X,
|
||||
Simulator::DEFAULT_REGION_SIZE_Y,
|
||||
false,
|
||||
),
|
||||
|size| (u32::from(size.size_x), u32::from(size.size_y), true),
|
||||
)
|
||||
}
|
||||
fn grid_item_type(value: u32) -> Option<GridItemType> {
|
||||
Some(match value {
|
||||
1 => GridItemType::Telehub,
|
||||
@@ -1124,22 +1216,100 @@ mod tests {
|
||||
.build()
|
||||
.unwrap();
|
||||
let manager = GridManager::native_new(Arc::new(client)).unwrap();
|
||||
manager.inner.insert_region(GridRegion {
|
||||
access: SimAccess::PG,
|
||||
agents: 0,
|
||||
map_image_id: UUID::zero(),
|
||||
name: "Example".into(),
|
||||
region_flags: RegionFlags::NONE,
|
||||
region_handle: (256_u64 << 32) | 512,
|
||||
water_height: 20,
|
||||
x: 1,
|
||||
y: 2,
|
||||
});
|
||||
manager.inner.insert_region(
|
||||
GridRegion {
|
||||
access: SimAccess::PG,
|
||||
agents: 0,
|
||||
map_image_id: UUID::zero(),
|
||||
name: "Example".into(),
|
||||
region_flags: RegionFlags::NONE,
|
||||
region_handle: (256_u64 << 32) | 512,
|
||||
size_x: 256,
|
||||
size_y: 256,
|
||||
water_height: 20,
|
||||
x: 1,
|
||||
y: 2,
|
||||
},
|
||||
false,
|
||||
);
|
||||
assert!(manager.inner.region_by_name("example").is_some());
|
||||
seconds.store(1_000 + CACHE_TTL.as_secs() + 1, Ordering::Relaxed);
|
||||
assert!(manager.inner.region_by_name("example").is_none());
|
||||
}
|
||||
#[test]
|
||||
fn map_block_size_uses_varregion_data_and_standard_fallback() {
|
||||
let mut size =
|
||||
crate::packets::MapBlockReplyPacketSizeBlock::new_with_constructor().unwrap();
|
||||
size.size_x = 1024;
|
||||
size.size_y = 1024;
|
||||
assert_eq!(map_block_size(Some(&size)), (1024, 1024, true));
|
||||
assert_eq!(map_block_size(None), (256, 256, false));
|
||||
}
|
||||
#[test]
|
||||
fn fallback_map_block_does_not_downgrade_an_explicit_size() {
|
||||
let manager = GridManager::native_new(Arc::new(GridClient::new().unwrap())).unwrap();
|
||||
let region = GridRegion {
|
||||
access: SimAccess::PG,
|
||||
agents: 0,
|
||||
map_image_id: UUID::zero(),
|
||||
name: "Varregion".into(),
|
||||
region_flags: RegionFlags::NONE,
|
||||
region_handle: (256_u64 << 32) | 512,
|
||||
size_x: 1024,
|
||||
size_y: 1024,
|
||||
water_height: 20,
|
||||
x: 1,
|
||||
y: 2,
|
||||
};
|
||||
manager.inner.insert_region(region.clone(), true);
|
||||
manager.inner.insert_region(
|
||||
GridRegion {
|
||||
size_x: 256,
|
||||
size_y: 256,
|
||||
..region
|
||||
},
|
||||
false,
|
||||
);
|
||||
let cached = manager
|
||||
.inner
|
||||
.region_by_handle((256_u64 << 32) | 512)
|
||||
.unwrap();
|
||||
assert_eq!((cached.size_x, cached.size_y), (1024, 1024));
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn region_reply_receiver_filters_the_event_stream() {
|
||||
let client = Arc::new(GridClient::new().unwrap());
|
||||
let manager = GridManager::native_new(client).unwrap();
|
||||
let handle = (256_u64 << 32) | 512;
|
||||
let desired = GridRegion {
|
||||
access: SimAccess::PG,
|
||||
agents: 0,
|
||||
map_image_id: UUID::zero(),
|
||||
name: "Varregion".into(),
|
||||
region_flags: RegionFlags::NONE,
|
||||
region_handle: handle,
|
||||
size_x: 1024,
|
||||
size_y: 1024,
|
||||
water_height: 20,
|
||||
x: 1,
|
||||
y: 2,
|
||||
};
|
||||
let (_subscription, mut receiver) =
|
||||
manager.region_reply_receiver(move |region| region.region_handle == handle);
|
||||
let mut unrelated = desired.clone();
|
||||
unrelated.region_handle += 1;
|
||||
manager
|
||||
.inner
|
||||
.events
|
||||
.region
|
||||
.emit(GridRegionEventArgs { region: unrelated });
|
||||
assert!(receiver.try_recv().unwrap().is_none());
|
||||
manager.inner.events.region.emit(GridRegionEventArgs {
|
||||
region: desired.clone(),
|
||||
});
|
||||
assert_eq!(receiver.await.unwrap(), desired);
|
||||
}
|
||||
#[test]
|
||||
fn simulator_features_preserve_unknown_fields() {
|
||||
let features = SimulatorFeatures::default();
|
||||
let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([
|
||||
|
||||
@@ -1183,6 +1183,7 @@ pub struct SimulatorData {
|
||||
pub sim_version: String,
|
||||
pub size_x: u32,
|
||||
pub size_y: u32,
|
||||
resolved_size: RwLock<(u32, u32)>,
|
||||
pub stats: SimulatorSimStats,
|
||||
pub terrain: RwLock<Vec<TerrainPatch>>,
|
||||
pub terrain_base0: UUID,
|
||||
@@ -1379,6 +1380,19 @@ impl Simulator {
|
||||
*write(&self.data.prey_id) = value;
|
||||
}
|
||||
|
||||
/// Returns the map-resolved region dimensions. Before the startup query
|
||||
/// completes this contains the login dimensions or the 256 m fallback.
|
||||
#[must_use]
|
||||
pub fn region_size(&self) -> (u32, u32) {
|
||||
*read(&self.data.resolved_size)
|
||||
}
|
||||
|
||||
pub(crate) fn native_set_region_size(&self, size_x: u32, size_y: u32) {
|
||||
if size_x != 0 && size_y != 0 {
|
||||
*write(&self.data.resolved_size) = (size_x, size_y);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn native_new(
|
||||
client: GridClient,
|
||||
address: std::net::SocketAddr,
|
||||
@@ -1429,6 +1443,10 @@ impl Simulator {
|
||||
sim_version: String::new(),
|
||||
size_x: size_x.unwrap_or(Self::DEFAULT_REGION_SIZE_X),
|
||||
size_y: size_y.unwrap_or(Self::DEFAULT_REGION_SIZE_Y),
|
||||
resolved_size: RwLock::new((
|
||||
size_x.unwrap_or(Self::DEFAULT_REGION_SIZE_X),
|
||||
size_y.unwrap_or(Self::DEFAULT_REGION_SIZE_Y),
|
||||
)),
|
||||
stats: SimulatorSimStats::default(),
|
||||
terrain: RwLock::new(Vec::new()),
|
||||
terrain_base0: UUID::zero(),
|
||||
@@ -1636,10 +1654,11 @@ impl Simulator {
|
||||
return Ok(false);
|
||||
};
|
||||
let terrain = read(&self.terrain);
|
||||
let (size_x, _) = self.region_size();
|
||||
let per_edge = if terrain.len() > 256 {
|
||||
256
|
||||
} else {
|
||||
usize::try_from(self.size_x.max(16) / 16).map_err(|_| Error::Argument)?
|
||||
usize::try_from(size_x.max(16) / 16).map_err(|_| Error::Argument)?
|
||||
};
|
||||
if usize::try_from(x / 16).map_err(|_| Error::Argument)? >= per_edge
|
||||
|| usize::try_from(y / 16).map_err(|_| Error::Argument)?
|
||||
@@ -4405,4 +4424,14 @@ mod tests {
|
||||
);
|
||||
assert!((height - 27.0).abs() < f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queried_region_size_replaces_the_login_fallback() {
|
||||
let client = GridClient::new().expect("client");
|
||||
let simulator =
|
||||
Simulator::new(client, "127.0.0.1:14003".parse().unwrap(), 13, None, None).unwrap();
|
||||
assert_eq!(simulator.region_size(), (256, 256));
|
||||
simulator.native_set_region_size(1024, 1024);
|
||||
assert_eq!(simulator.region_size(), (1024, 1024));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user