use crate::Entity;
use crate::abi::{
BlockPlace, Header, PropPlace, PropRemove, Status, TerrainEdit, TransactionInfo, TransactionOp,
VoxelRegion, VoxelWrite,
};
use crate::terrain::{Shape, add_edit, dig_edit, flatten_edit};
use crate::voxels::{VoxelBox, VoxelPos};
use crate::{Host, ItemId};
use super::{BuildRefusal, Player, PropState, World, WorldError, WorldTransform};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TransactionId(u64);
impl TransactionId {
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum TransactionStatus {
Pending,
Applied,
Undoing,
Undone,
Failed(WorldError),
NotAllowed(BuildRefusal),
Expired,
UndoRefused(WorldError),
}
impl TransactionStatus {
fn from_info(info: &TransactionInfo) -> Self {
let reason = || {
if info.holds != 0 {
return Some(WorldError::HoldsState(PropState::from_bits(info.holds)));
}
WorldError::check(info.reason, WorldError::Missing).err()
};
match info.state {
TransactionInfo::APPLIED => Self::Applied,
TransactionInfo::UNDOING => Self::Undoing,
TransactionInfo::UNDONE => Self::Undone,
TransactionInfo::FAILED => match BuildRefusal::from_code(info.refusal) {
Some(refusal) => Self::NotAllowed(refusal),
None => Self::Failed(reason().unwrap_or(WorldError::Missing)),
},
TransactionInfo::EXPIRED => Self::Expired,
TransactionInfo::UNDO_REFUSED => {
Self::UndoRefused(reason().unwrap_or(WorldError::Refused))
}
_ => Self::Pending,
}
}
}
#[derive(Debug)]
#[must_use = "a transaction changes nothing until it is committed"]
pub struct Transaction {
host: Host,
changes: Vec<Change>,
actor: Entity,
}
#[derive(Debug)]
enum Change {
Terrain(TerrainEdit),
Voxels(
VoxelWrite,
#[expect(
dead_code,
reason = "holds the voxels the write points at until the commit"
)]
Vec<crate::abi::Voxel>,
),
Erase(VoxelRegion),
Block(BlockPlace),
PlaceProp(PropPlace),
RemoveProp(PropRemove),
}
impl Change {
fn op(&self) -> TransactionOp {
let (kind, data) = match self {
Self::Terrain(edit) => (TransactionOp::TERRAIN, (&raw const *edit).cast()),
Self::Voxels(write, _) => (TransactionOp::VOXELS, (&raw const *write).cast()),
Self::Erase(region) => (TransactionOp::ERASE, (&raw const *region).cast()),
Self::Block(block) => (TransactionOp::BLOCK, (&raw const *block).cast()),
Self::PlaceProp(prop) => (TransactionOp::PLACE_PROP, (&raw const *prop).cast()),
Self::RemoveProp(prop) => (TransactionOp::REMOVE_PROP, (&raw const *prop).cast()),
};
TransactionOp {
kind,
reserved: 0,
data,
}
}
}
impl Transaction {
pub fn terrain_add(
mut self,
position: [f64; 3],
radius: f32,
height: f32,
material: u8,
shape: Shape,
) -> Self {
self.changes.push(Change::Terrain(add_edit(
position, radius, height, material, shape,
)));
self
}
pub fn terrain_dig(
mut self,
position: [f64; 3],
radius: f32,
shape: Shape,
protect: &[u8],
) -> Self {
self.changes
.push(Change::Terrain(dig_edit(position, radius, shape, protect)));
self
}
pub fn terrain_flatten(
mut self,
position: [f64; 3],
radius: f32,
material: u8,
shape: Shape,
) -> Self {
self.changes.push(Change::Terrain(flatten_edit(
position, radius, material, shape,
)));
self
}
pub fn write_voxels(mut self, voxels: &VoxelBox) -> Self {
let raw: Vec<crate::abi::Voxel> = voxels.as_slice().iter().map(|v| v.to_abi()).collect();
let write = VoxelWrite {
header: Header::of::<VoxelWrite>(),
min: voxels.min().to_array(),
size: voxels.size(),
voxels: raw.as_ptr(),
count: raw.len() as u64,
};
self.changes.push(Change::Voxels(write, raw));
self
}
pub fn erase_block(mut self, min: VoxelPos, size: [u32; 3]) -> Self {
self.changes.push(Change::Erase(VoxelRegion {
header: Header::of::<VoxelRegion>(),
min: min.to_array(),
size,
}));
self
}
pub fn place_block(mut self, shape: ItemId, material: ItemId, at: WorldTransform) -> Self {
self.changes.push(Change::Block(BlockPlace {
header: Header::of::<BlockPlace>(),
shape,
material,
position: at.position,
rotation: at.orientation,
}));
self
}
pub fn place_prop(mut self, item: ItemId, at: WorldTransform) -> Self {
self.changes.push(Change::PlaceProp(PropPlace {
header: Header::of::<PropPlace>(),
item,
reserved: 0,
position: at.position,
orientation: at.orientation,
}));
self
}
pub fn remove_prop(mut self, entity: Entity) -> Self {
self.changes.push(Change::RemoveProp(PropRemove {
header: Header::of::<PropRemove>(),
entity,
flags: 0,
}));
self
}
pub fn remove_prop_discarding_state(mut self, entity: Entity) -> Self {
self.changes.push(Change::RemoveProp(PropRemove {
header: Header::of::<PropRemove>(),
entity,
flags: PropRemove::DISCARD_STATE,
}));
self
}
#[must_use]
pub fn len(&self) -> usize {
self.changes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.changes.is_empty()
}
pub fn commit(self) -> Result<TransactionId, WorldError> {
let ops: Vec<TransactionOp> = self.changes.iter().map(Change::op).collect();
let count = u32::try_from(ops.len()).map_err(|_| WorldError::Refused)?;
let mut id = 0u64;
let api = self.host.api();
let status = if self.actor.is_none() {
(api.transaction_commit)(self.host.handle(), ops.as_ptr(), count, &raw mut id)
} else {
(api.transaction_commit_for)(
self.host.handle(),
self.actor,
ops.as_ptr(),
count,
&raw mut id,
)
};
WorldError::from_status(status).map(|()| TransactionId(id))
}
}
impl World {
pub fn transaction(self) -> Transaction {
Transaction {
host: self.0.host,
changes: Vec::new(),
actor: Entity::NONE,
}
}
pub fn transaction_for(self, player: &Player) -> Transaction {
Transaction {
actor: player.entity,
..self.transaction()
}
}
pub fn undo(self, id: TransactionId) -> Result<(), WorldError> {
let host = self.0.host;
WorldError::from_status((host.api().transaction_undo)(host.handle(), id.0))
}
#[must_use]
pub fn transaction_entities(self, id: TransactionId) -> Vec<Entity> {
let host = self.0.host;
let mut count = 0u32;
let api = host.api();
if (api.transaction_entities)(
host.handle(),
id.0,
core::ptr::null_mut(),
0,
&raw mut count,
) != Status::OK
{
return Vec::new();
}
let mut raw = vec![Entity::NONE; count as usize];
let capacity = u32::try_from(raw.len()).unwrap_or(u32::MAX);
if (api.transaction_entities)(
host.handle(),
id.0,
raw.as_mut_ptr(),
capacity,
&raw mut count,
) != Status::OK
{
return Vec::new();
}
raw.truncate(count as usize);
raw
}
#[must_use]
pub fn transaction_status(self, id: TransactionId) -> Option<TransactionStatus> {
let host = self.0.host;
let mut info = TransactionInfo {
header: Header::of::<TransactionInfo>(),
state: TransactionInfo::PENDING,
reason: Status::OK,
ops: 0,
applied: 0,
bricks: 0,
refusal: 0,
reserved: 0,
holds: 0,
created: 0,
};
let status = (host.api().transaction_status)(host.handle(), id.0, &raw mut info);
status.is_ok().then(|| TransactionStatus::from_info(&info))
}
}