Index: source/simulation2/components/CCmpObstruction.cpp =================================================================== --- source/simulation2/components/CCmpObstruction.cpp +++ source/simulation2/components/CCmpObstruction.cpp @@ -23,8 +23,8 @@ #include "ps/CLogger.h" #include "simulation2/MessageTypes.h" #include "simulation2/components/ICmpObstructionManager.h" +#include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpTerrain.h" -#include "simulation2/components/ICmpUnitMotion.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/serialization/SerializeTemplates.h" @@ -43,32 +43,26 @@ DEFAULT_COMPONENT_ALLOCATOR(Obstruction) - typedef ICmpObstructionManager::tag_t tag_t; - typedef ICmpObstructionManager::flags_t flags_t; - - // Template state: + using tag_t = ICmpObstructionManager::tag_t; + using flags_t = ICmpObstructionManager::flags_t; EObstructionType m_Type; - - entity_pos_t m_Size0; // radius or width - entity_pos_t m_Size1; // radius or depth flags_t m_TemplateFlags; - entity_pos_t m_Clearance; - typedef struct { - entity_pos_t dx, dz; - entity_angle_t da; + struct Shape { + Shape(entity_pos_t s0, entity_pos_t s1, entity_pos_t x, entity_pos_t z) + : size0(s0), size1(s1), dx(x), dz(z) {}; entity_pos_t size0, size1; - flags_t flags; - } Shape; - + entity_pos_t dx, dz; + // TODO: handle angled shapes? + }; std::vector m_Shapes; - // Dynamic state: + std::vector m_Tags; + + // Cached maximal bounds. + entity_pos_t m_Size0, m_Size1; - /// Whether the entity associated with this obstruction is currently moving. Only applicable for - /// UNIT-type obstructions. - bool m_Moving; /// Whether an obstruction's control group should be kept consistent and /// used to set control groups for entities that collide with it. bool m_ControlPersist; @@ -88,15 +82,7 @@ * These are only necessary in case it is not sufficient for an entity to belong to only one control * group. Otherwise, they can be ignored. */ - entity_id_t m_ControlGroup2; - - /// Identifier of this entity's obstruction shape, as registered in the obstruction manager. Contains - /// structure, but should be treated as opaque here. - tag_t m_Tag; - std::vector m_ClusterTags; - - /// Set of flags affecting the behaviour of this entity's obstruction shape. - flags_t m_Flags; + entity_id_t m_ControlGroup2 = INVALID_ENTITY; static std::string GetSchema() { @@ -176,7 +162,6 @@ { // The minimum obstruction size is the navcell size * sqrt(2) // This is enforced in the schema as a minimum of 1.5 - fixed minObstruction = (Pathfinding::NAVCELL_SIZE.Square() * 2).Sqrt(); m_TemplateFlags = 0; if (paramNode.GetChild("BlockMovement").ToBool()) m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_MOVEMENT; @@ -186,103 +171,82 @@ m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_FOUNDATION; if (paramNode.GetChild("DeleteUponConstruction").ToBool()) m_TemplateFlags |= ICmpObstructionManager::FLAG_DELETE_UPON_CONSTRUCTION; - - m_Flags = m_TemplateFlags; if (paramNode.GetChild("DisableBlockMovement").ToBool()) - m_Flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT); + m_TemplateFlags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT); if (paramNode.GetChild("DisableBlockPathfinding").ToBool()) - m_Flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING); + m_TemplateFlags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING); + + m_ControlPersist = paramNode.GetChild("ControlPersist").ToBool(); + + m_ControlGroup = GetEntityId(); if (paramNode.GetChild("Unit").IsOk()) - { + // Unit shapes have only a circular clearance, coming from their passability class. + // This is initalized by UnitMotion directly (NB: this means component initialization order is important). + // If that is ever a problem, Obstruction should subscribe to MT_Create and do that there. m_Type = UNIT; - - CmpPtr cmpUnitMotion(GetEntityHandle()); - if (cmpUnitMotion) - m_Clearance = cmpUnitMotion->GetUnitClearance(); - } else if (paramNode.GetChild("Static").IsOk()) { m_Type = STATIC; - m_Size0 = paramNode.GetChild("Static").GetChild("@width").ToFixed(); - m_Size1 = paramNode.GetChild("Static").GetChild("@depth").ToFixed(); - ENSURE(m_Size0 > minObstruction); - ENSURE(m_Size1 > minObstruction); + m_Shapes.emplace_back( + paramNode.GetChild("Static").GetChild("@width").ToFixed(), + paramNode.GetChild("Static").GetChild("@depth").ToFixed(), + fixed::Zero(), fixed::Zero() + ); + m_Size0 = m_Shapes.front().size0; + m_Size1 = m_Shapes.front().size1; } else { m_Type = CLUSTER; - CFixedVector2D max = CFixedVector2D(fixed::FromInt(0), fixed::FromInt(0)); - CFixedVector2D min = CFixedVector2D(fixed::FromInt(0), fixed::FromInt(0)); - const CParamNode::ChildrenMap& clusterMap = paramNode.GetChild("Obstructions").GetChildren(); - for(CParamNode::ChildrenMap::const_iterator it = clusterMap.begin(); it != clusterMap.end(); ++it) + const CParamNode::ChildrenMap& obstructions = paramNode.GetChild("Obstructions").GetChildren(); + if (obstructions.empty()) + return; + CParamNode::ChildrenMap::const_iterator it = obstructions.begin(); + m_Shapes.emplace_back( + it->second.GetChild("@width").ToFixed(), + it->second.GetChild("@depth").ToFixed(), + it->second.GetChild("@x").ToFixed(), + it->second.GetChild("@z").ToFixed() + ); + // NB: the x,z coordinates are actually the center of the shape + // but we're only interested in the size, so to avoid needless divisions, we shift. + entity_pos_t min0 = m_Shapes.back().dx; + entity_pos_t max0 = m_Shapes.back().dx + m_Shapes.back().size0; + entity_pos_t min1 = m_Shapes.back().dz; + entity_pos_t max1 = m_Shapes.back().dz + m_Shapes.back().size1; + while (++it != obstructions.end()) { - Shape b; - b.size0 = it->second.GetChild("@width").ToFixed(); - b.size1 = it->second.GetChild("@depth").ToFixed(); - ENSURE(b.size0 > minObstruction); - ENSURE(b.size1 > minObstruction); - b.dx = it->second.GetChild("@x").ToFixed(); - b.dz = it->second.GetChild("@z").ToFixed(); - b.da = entity_angle_t::FromInt(0); - b.flags = m_Flags; - m_Shapes.push_back(b); - max.X = std::max(max.X, b.dx + b.size0/2); - max.Y = std::max(max.Y, b.dz + b.size1/2); - min.X = std::min(min.X, b.dx - b.size0/2); - min.Y = std::min(min.Y, b.dz - b.size1/2); + m_Shapes.emplace_back( + it->second.GetChild("@width").ToFixed(), + it->second.GetChild("@depth").ToFixed(), + it->second.GetChild("@x").ToFixed(), + it->second.GetChild("@z").ToFixed() + ); + min0 = std::min(min0, m_Shapes.back().dx); + max0 = std::min(max0, m_Shapes.back().dx + m_Shapes.back().size0); + min1 = std::min(min1, m_Shapes.back().dz); + max1 = std::min(max1, m_Shapes.back().dz + m_Shapes.back().size1); } - m_Size0 = fixed::FromInt(2).Multiply(std::max(max.X, -min.X)); - m_Size1 = fixed::FromInt(2).Multiply(std::max(max.Y, -min.Y)); + m_Size0 = max0 - min0; + m_Size1 = max1 - min1; } - - m_ControlPersist = paramNode.GetChild("ControlPersist").IsOk(); - - m_Tag = tag_t(); - if (m_Type == CLUSTER) - m_ClusterTags.clear(); - m_Moving = false; - m_ControlGroup = GetEntityId(); - m_ControlGroup2 = INVALID_ENTITY; } virtual void Deinit() { } - struct SerializeTag + virtual void Serialize(ISerializer&) { - template - void operator()(S& serialize, const char* UNUSED(name), tag_t& value) - { - serialize.NumberU32_Unbounded("tag", value.n); - } - }; - - template - void SerializeCommon(S& serialize) - { - serialize.Bool("moving", m_Moving); - serialize.NumberU32_Unbounded("control group", m_ControlGroup); - serialize.NumberU32_Unbounded("control group 2", m_ControlGroup2); - serialize.NumberU32_Unbounded("tag", m_Tag.n); - serialize.NumberU8_Unbounded("flags", m_Flags); - if (m_Type == CLUSTER) - SerializeVector()(serialize, "cluster tags", m_ClusterTags); - if (m_Type == UNIT) - serialize.NumberFixed_Unbounded("clearance", m_Clearance); - } - - virtual void Serialize(ISerializer& serialize) - { - SerializeCommon(serialize); + // This class should have nothing to serialize. + // All dynamic state is kept in the Obstruction Manager directly, + // which enables threading the pathfinder. } - virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) + virtual void Deserialize(const CParamNode& paramNode, IDeserializer&) { Init(paramNode); - - SerializeCommon(deserialize); } virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) @@ -293,147 +257,157 @@ { const CMessagePositionChanged& data = static_cast (msg); - if (!data.inWorld && !m_Tag.valid()) - break; // nothing needs to change - - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (!cmpObstructionManager) - break; // error - - if (data.inWorld && m_Tag.valid()) - { - cmpObstructionManager->MoveShape(m_Tag, data.x, data.z, data.a); - - if (m_Type == CLUSTER) - { - for (size_t i = 0; i < m_Shapes.size(); ++i) - { - Shape& b = m_Shapes[i]; - fixed s, c; - sincos_approx(data.a, s, c); - cmpObstructionManager->MoveShape(m_ClusterTags[i], data.x + b.dx.Multiply(c) + b.dz.Multiply(s), data.z + b.dz.Multiply(c) - b.dx.Multiply(s), data.a + b.da); - } - } - } - else if (data.inWorld && !m_Tag.valid()) - { - // Need to create a new pathfinder shape: - if (m_Type == STATIC) - m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), - data.x, data.z, data.a, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2); - else if (m_Type == UNIT) - m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(), - data.x, data.z, m_Clearance, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup); - else - AddClusterShapes(data.x, data.x, data.a); - } - else if (!data.inWorld && m_Tag.valid()) - { - cmpObstructionManager->RemoveShape(m_Tag); - m_Tag = tag_t(); - if(m_Type == CLUSTER) - RemoveClusterShapes(); - } + if (!data.inWorld && m_Tags.empty()) + break; // Not in world before, not now => nothing needs to change. + else if (!data.inWorld) + Unregister(); + else if (data.inWorld && m_Tags.empty()) + Register(data.x, data.z, data.a); + else + MoveShape(data.x, data.z, data.a); break; } case MT_Destroy: { - if (m_Tag.valid()) - { - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (!cmpObstructionManager) - break; // error - - // Deactivate the obstruction in case PositionChanged messages are sent after this. - cmpObstructionManager->RemoveShape(m_Tag); - m_Tag = tag_t(); - if(m_Type == CLUSTER) - RemoveClusterShapes(); - } + Unregister(); break; } } } - virtual void SetDisableBlockMovementPathfinding(bool movementDisabled, bool pathfindingDisabled, int32_t shape) + void Unregister() { - flags_t *flags = NULL; - if (shape == -1) - flags = &m_Flags; - else if (m_Type == CLUSTER && shape < (int32_t)m_Shapes.size()) - flags = &m_Shapes[shape].flags; - else - return; // error + if (m_Tags.empty()) + return; - // Remove the blocking / pathfinding flags or - // Add the blocking / pathfinding flags if the template had enabled them - if (movementDisabled) - *flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT); - else - *flags |= (flags_t)(m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT); - if (pathfindingDisabled) - *flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING); - else - *flags |= (flags_t)(m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_PATHFINDING); - - // Reset the shape with the new flags (kind of inefficiently - we - // should have a ICmpObstructionManager::SetFlags function or something) - SetActive(false); - SetActive(true); + CmpPtr cmpObstructionManager(GetSystemEntity()); + for (size_t i = 0; i < m_Tags.size(); ++i) + cmpObstructionManager->RemoveShape(m_Tags[i]); + m_Tags.clear(); } - void SetActive(bool active) + void Register(entity_pos_t x, entity_pos_t z, entity_angle_t a) { - if (active) - { - // Construct the obstruction shape - - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (!cmpObstructionManager) - return; // error - - CmpPtr cmpPosition(GetEntityHandle()); - if (!cmpPosition) - return; // error + CmpPtr cmpObstructionManager(GetSystemEntity()); + ENSURE(cmpObstructionManager); + if (m_Type == UNIT) + m_Tags.emplace_back(cmpObstructionManager->AddUnitShape(GetEntityId(), x, z, m_Size0, m_TemplateFlags, m_ControlGroup)); + else if (m_Shapes.empty()) + return; // Can happen with 'obstructions' type, if there are none specified. + else + for (const Shape& shape : m_Shapes) + { + // Move the dx, dz coordinates if necessary. + entity_pos_t xx = x; + entity_pos_t zz = z; + if (a != fixed::Zero() && (shape.dx != fixed::Zero() || shape.dz != fixed::Zero())) + { + fixed s, c; + sincos_approx(a, s, c); + xx += shape.dx.Multiply(c); + zz += shape.dz.Multiply(s); + } + m_Tags.emplace_back(cmpObstructionManager->AddStaticShape(GetEntityId(), xx, zz, a, shape.size0, shape.size1, m_TemplateFlags, m_ControlGroup, m_ControlGroup2)); + } + } - if (!cmpPosition->IsInWorld()) - return; // don't need an obstruction + void MoveShape(entity_pos_t x, entity_pos_t z, entity_angle_t a) + { + CmpPtr cmpObstructionManager(GetSystemEntity()); + ENSURE(cmpObstructionManager); - // TODO: code duplication from message handlers - CFixedVector2D pos = cmpPosition->GetPosition2D(); - if (m_Type == STATIC) - m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), - pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2); - else if (m_Type == UNIT) - m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(), - pos.X, pos.Y, m_Clearance, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup); - else - AddClusterShapes(pos.X, pos.Y, cmpPosition->GetRotation().Y); + if (m_Type == UNIT) + { + cmpObstructionManager->MoveShape(m_Tags.front(), x, z, a); + return; } - else if (!active) + for (size_t i = 0; i < m_Shapes.size(); ++i) { - // Delete the obstruction shape - - // TODO: code duplication from message handlers - if (m_Tag.valid()) + // Move the dx, dz coordinates if necessary. + Shape& shape = m_Shapes[i]; + entity_pos_t xx = x; + entity_pos_t zz = z; + if (a != fixed::Zero() && (shape.dx != fixed::Zero() || shape.dz != fixed::Zero())) { - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (!cmpObstructionManager) - return; // error - - cmpObstructionManager->RemoveShape(m_Tag); - m_Tag = tag_t(); - if (m_Type == CLUSTER) - RemoveClusterShapes(); + fixed s, c; + sincos_approx(a, s, c); + xx += shape.dx.Multiply(c) + shape.dz.Multiply(s); + zz += shape.dz.Multiply(c) - shape.dx.Multiply(s); } + cmpObstructionManager->MoveShape(m_Tags[i], xx, zz, a); } } + // These arguments are somewhat confusing, but "true" means "do disable the flag". + virtual void SetDisableBlockMovementPathfinding(bool dontBlockMovement, bool dontBlockPathfinding, int32_t shape) + { + CmpPtr cmpObstructionManager(GetSystemEntity()); + ENSURE(cmpObstructionManager); + + if (!dontBlockMovement) + dontBlockMovement = !(m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT); + if (!dontBlockPathfinding) + dontBlockPathfinding = !(m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_PATHFINDING); + + for (size_t i = (shape == -1 ? 0 : shape); i < (shape == -1 ? m_Shapes.size() : shape+1); ++i) + cmpObstructionManager->SetFlags(m_Tags[i], !dontBlockMovement, !dontBlockPathfinding); + } + virtual bool GetBlockMovementFlag() const { return (m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT) != 0; } + virtual void SetMovingFlag(bool enabled) + { + if (m_Type != UNIT || m_Tags.empty()) + return; + CmpPtr cmpObstructionManager(GetSystemEntity()); + cmpObstructionManager->SetUnitMovingFlag(m_Tags.front(), enabled); + } + + virtual bool IsControlPersistent() const + { + return m_ControlPersist; + } + + virtual void SetControlGroup(entity_id_t group) + { + m_ControlGroup = group; + UpdateControlGroups(); + } + + virtual void SetControlGroup2(entity_id_t group2) + { + m_ControlGroup2 = group2; + UpdateControlGroups(); + } + + virtual entity_id_t GetControlGroup() const + { + return m_ControlGroup; + } + + virtual entity_id_t GetControlGroup2() const + { + return m_ControlGroup2; + } + + void UpdateControlGroups() + { + if (m_Tags.empty()) + return; + + CmpPtr cmpObstructionManager(GetSystemEntity()); + ENSURE(cmpObstructionManager); + + if (m_Type == UNIT) + cmpObstructionManager->SetUnitControlGroup(m_Tags.front(), m_ControlGroup); + else + for (const tag_t& tag : m_Tags) + cmpObstructionManager->SetStaticControlGroup(tag, m_ControlGroup, m_ControlGroup2); + } + virtual EObstructionType GetObstructionType() const { return m_Type; @@ -441,7 +415,7 @@ virtual ICmpObstructionManager::tag_t GetObstruction() const { - return m_Tag; + return m_Tags.empty() ? tag_t{} : m_Tags.front(); } virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) const @@ -457,15 +431,11 @@ virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out, bool previousPosition) const { CmpPtr cmpPosition(GetEntityHandle()); - if (!cmpPosition) - return false; // error + if (!cmpPosition || !cmpPosition->IsInWorld()) + return false; CmpPtr cmpObstructionManager(GetSystemEntity()); - if (!cmpObstructionManager) - return false; // error - - if (!cmpPosition->IsInWorld()) - return false; // no obstruction square + ENSURE(cmpObstructionManager); CFixedVector2D pos; if (previousPosition) @@ -473,7 +443,7 @@ else pos = cmpPosition->GetPosition2D(); if (m_Type == UNIT) - out = cmpObstructionManager->GetUnitShapeObstruction(pos.X, pos.Y, m_Clearance); + out = cmpObstructionManager->GetUnitShapeObstruction(pos.X, pos.Y, m_Size0); else out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1); return true; @@ -482,7 +452,7 @@ virtual entity_pos_t GetSize() const { if (m_Type == UNIT) - return m_Clearance; + return m_Size0; else return CFixedVector2D(m_Size0 / 2, m_Size1 / 2).Length(); } @@ -495,12 +465,10 @@ virtual void SetUnitClearance(const entity_pos_t& clearance) { if (m_Type == UNIT) - m_Clearance = clearance; - } - - virtual bool IsControlPersistent() const - { - return m_ControlPersist; + { + m_Size0 = clearance; + m_Size1 = clearance; + } } virtual bool CheckShorePlacement() const @@ -536,8 +504,6 @@ if (!cmpPosition->IsInWorld()) return FOUNDATION_CHECK_FAIL_NO_OBSTRUCTION; // no obstruction - CFixedVector2D pos = cmpPosition->GetPosition2D(); - CmpPtr cmpPathfinder(GetSystemEntity()); if (!cmpPathfinder) return FOUNDATION_CHECK_FAIL_ERROR; // error @@ -558,8 +524,10 @@ SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION); + CFixedVector2D pos = cmpPosition->GetPosition2D(); + if (m_Type == UNIT) - return cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, m_Clearance, passClass, onlyCenterPoint); + return cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, m_Size0, passClass, onlyCenterPoint); else return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass, onlyCenterPoint); } @@ -587,11 +555,11 @@ } // Ignore collisions with entities unless they block foundations and match both control groups. - SkipTagRequireControlGroupsAndFlagObstructionFilter filter(m_Tag, m_ControlGroup, m_ControlGroup2, + SkipTagRequireControlGroupsAndFlagObstructionFilter filter(m_Tags.empty() ? tag_t{} : m_Tags.front(), m_ControlGroup, m_ControlGroup2, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION); if (m_Type == UNIT) - return !cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Clearance, NULL); + return !cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, NULL); else return !cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, NULL ); } @@ -636,67 +604,6 @@ return GetEntitiesByFlags(ICmpObstructionManager::FLAG_DELETE_UPON_CONSTRUCTION); } - virtual void SetMovingFlag(bool enabled) - { - m_Moving = enabled; - - if (m_Tag.valid() && m_Type == UNIT) - { - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (cmpObstructionManager) - cmpObstructionManager->SetUnitMovingFlag(m_Tag, m_Moving); - } - } - - virtual void SetControlGroup(entity_id_t group) - { - m_ControlGroup = group; - UpdateControlGroups(); - } - - virtual void SetControlGroup2(entity_id_t group2) - { - m_ControlGroup2 = group2; - UpdateControlGroups(); - } - - virtual entity_id_t GetControlGroup() const - { - return m_ControlGroup; - } - - virtual entity_id_t GetControlGroup2() const - { - return m_ControlGroup2; - } - - void UpdateControlGroups() - { - if (m_Tag.valid()) - { - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (cmpObstructionManager) - { - if (m_Type == UNIT) - { - cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup); - } - else if (m_Type == STATIC) - { - cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2); - } - else - { - cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2); - for (size_t i = 0; i < m_ClusterTags.size(); ++i) - { - cmpObstructionManager->SetStaticControlGroup(m_ClusterTags[i], m_ControlGroup, m_ControlGroup2); - } - } - } - } - } - void ResolveFoundationCollisions() const { if (m_Type == UNIT) @@ -768,50 +675,6 @@ } } } -protected: - - inline void AddClusterShapes(entity_pos_t x, entity_pos_t z, entity_angle_t a) - { - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (!cmpObstructionManager) - return; // error - - flags_t flags = m_Flags; - // Disable block movement and block pathfinding for the obstruction shape - flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT); - flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING); - - m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), - x, z, a, m_Size0, m_Size1, flags, m_ControlGroup, m_ControlGroup2); - - fixed s, c; - sincos_approx(a, s, c); - - for (size_t i = 0; i < m_Shapes.size(); ++i) - { - Shape& b = m_Shapes[i]; - tag_t tag = cmpObstructionManager->AddStaticShape(GetEntityId(), - x + b.dx.Multiply(c) + b.dz.Multiply(s), z + b.dz.Multiply(c) - b.dx.Multiply(s), a + b.da, b.size0, b.size1, b.flags, m_ControlGroup, m_ControlGroup2); - m_ClusterTags.push_back(tag); - } - } - - inline void RemoveClusterShapes() - { - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (!cmpObstructionManager) - return; // error - - for (size_t i = 0; i < m_ClusterTags.size(); ++i) - { - if (m_ClusterTags[i].valid()) - { - cmpObstructionManager->RemoveShape(m_ClusterTags[i]); - } - } - m_ClusterTags.clear(); - } - }; REGISTER_COMPONENT_TYPE(Obstruction) Index: source/simulation2/components/CCmpObstructionManager.cpp =================================================================== --- source/simulation2/components/CCmpObstructionManager.cpp +++ source/simulation2/components/CCmpObstructionManager.cpp @@ -35,8 +35,11 @@ #include "graphics/Terrain.h" #include "maths/MathUtil.h" #include "ps/Profile.h" -#include "renderer/Scene.h" #include "ps/CLogger.h" +#include "renderer/Scene.h" + +#include +#include // Externally, tags are opaque non-zero positive integers. // Internally, they are tagged (by shape) indexes into shape lists. @@ -147,6 +150,18 @@ entity_pos_t m_WorldZ1; u16 m_TerrainTiles; + /** + * The obstruction manager can update the state instantly, or defer them for later. + * When instant-mode is active, the obstruction manager lock the mutex. + * This is used so that the pathfinder can compute paths asynchronously without risking + * that the state changes non-deterministically, while still allowing instant changes when convenient + * (there is a fair amount of JS code that would be annoying if everything had to wait one turn). + * TODO: actually implement deferred mode. + */ + std::atomic m_InstantMode; + std::mutex m_InstantModeLock; + + static std::string GetSchema() { return ""; @@ -168,6 +183,10 @@ m_WorldX0 = m_WorldZ0 = m_WorldX1 = m_WorldZ1 = entity_pos_t::Zero(); m_TerrainTiles = 0; + // Start in instant-mode for convenience in tests and such. + m_InstantModeLock.lock(); + m_InstantMode = true; + // Initialise with bogus values (these will get replaced when // SetBounds is called) ResetSubdivisions(entity_pos_t::FromInt(1024), entity_pos_t::FromInt(1024)); @@ -233,6 +252,8 @@ // So anything that happens here should be safely serialized. virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) { + ENSURE(m_InstantMode); + m_WorldX0 = x0; m_WorldZ0 = z0; m_WorldX1 = x1; @@ -257,6 +278,8 @@ void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1) { + ENSURE(m_InstantMode); + // Use 8x8 tile subdivisions // (TODO: find the optimal number instead of blindly guessing) m_UnitSubdivision.Reset(x1, z1, entity_pos_t::FromInt(8*TERRAIN_TILE_SIZE)); @@ -279,6 +302,8 @@ virtual tag_t AddUnitShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_pos_t clearance, flags_t flags, entity_id_t group) { + ENSURE(m_InstantMode); + UnitShape shape = { ent, x, z, clearance, flags, group }; u32 id = m_UnitShapeNext++; m_UnitShapes[id] = shape; @@ -292,6 +317,8 @@ virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h, flags_t flags, entity_id_t group, entity_id_t group2 /* = INVALID_ENTITY */) { + ENSURE(m_InstantMode); + fixed s, c; sincos_approx(a, s, c); CFixedVector2D u(c, -s); @@ -331,6 +358,7 @@ virtual void MoveShape(tag_t tag, entity_pos_t x, entity_pos_t z, entity_angle_t a) { + ENSURE(m_InstantMode); ENSURE(TAG_IS_VALID(tag)); if (TAG_IS_UNIT(tag)) @@ -378,8 +406,43 @@ } } + virtual void SetFlags(tag_t tag, bool blockMovement, bool blockPathfinding) + { + ENSURE(m_InstantMode); + ENSURE(TAG_IS_VALID(tag)); + + if (TAG_IS_UNIT(tag)) + { + UnitShape& shape = m_UnitShapes[TAG_TO_INDEX(tag)]; + if (blockMovement) + shape.flags |= FLAG_BLOCK_MOVEMENT; + else + shape.flags &= (flags_t)~FLAG_BLOCK_MOVEMENT; + if (blockPathfinding) + shape.flags |= FLAG_BLOCK_PATHFINDING; + else + shape.flags &= (flags_t)~FLAG_BLOCK_PATHFINDING; + MakeDirtyUnit(FLAG_BLOCK_MOVEMENT | FLAG_BLOCK_PATHFINDING, TAG_TO_INDEX(tag), shape); + } + else + { + StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)]; + if (blockMovement) + shape.flags |= FLAG_BLOCK_MOVEMENT; + else + shape.flags &= (flags_t)~FLAG_BLOCK_MOVEMENT; + if (blockPathfinding) + shape.flags |= FLAG_BLOCK_PATHFINDING; + else + shape.flags &= (flags_t)~FLAG_BLOCK_PATHFINDING; + MakeDirtyStatic(FLAG_BLOCK_MOVEMENT | FLAG_BLOCK_PATHFINDING, TAG_TO_INDEX(tag), shape); + } + MakeDirtyDebug(); + } + virtual void SetUnitMovingFlag(tag_t tag, bool moving) { + ENSURE(m_InstantMode); ENSURE(TAG_IS_VALID(tag) && TAG_IS_UNIT(tag)); if (TAG_IS_UNIT(tag)) @@ -396,6 +459,7 @@ virtual void SetUnitControlGroup(tag_t tag, entity_id_t group) { + ENSURE(m_InstantMode); ENSURE(TAG_IS_VALID(tag) && TAG_IS_UNIT(tag)); if (TAG_IS_UNIT(tag)) @@ -407,6 +471,7 @@ virtual void SetStaticControlGroup(tag_t tag, entity_id_t group, entity_id_t group2) { + ENSURE(m_InstantMode); ENSURE(TAG_IS_VALID(tag) && TAG_IS_STATIC(tag)); if (TAG_IS_STATIC(tag)) @@ -419,6 +484,7 @@ virtual void RemoveShape(tag_t tag) { + ENSURE(m_InstantMode); ENSURE(TAG_IS_VALID(tag)); if (TAG_IS_UNIT(tag)) @@ -491,6 +557,8 @@ virtual void SetPassabilityCircular(bool enabled) { + ENSURE(m_InstantMode); + m_PassabilityCircular = enabled; MakeDirtyAll(); Index: source/simulation2/components/ICmpObstruction.h =================================================================== --- source/simulation2/components/ICmpObstruction.h +++ source/simulation2/components/ICmpObstruction.h @@ -128,6 +128,11 @@ virtual void SetMovingFlag(bool enabled) = 0; + /** + * Enable/disable the "block movement" and "block pathfinding" flags. + * @param shape - The index of the obstruction (in alphabetical order, not template order), or -1 for all obstructions + * (this is only really relevant for cluster obstruction shapes, just pass 0 otherwise). + */ virtual void SetDisableBlockMovementPathfinding(bool movementDisabled, bool pathfindingDisabled, int32_t shape) = 0; virtual bool GetBlockMovementFlag() const = 0; Index: source/simulation2/components/ICmpObstructionManager.h =================================================================== --- source/simulation2/components/ICmpObstructionManager.h +++ source/simulation2/components/ICmpObstructionManager.h @@ -146,6 +146,11 @@ */ virtual void MoveShape(tag_t tag, entity_pos_t x, entity_pos_t z, entity_angle_t a) = 0; + /** + * Set whether a shape should block movement or pathfinding. + */ + virtual void SetFlags(tag_t tag, bool blockMovement, bool blockPathfinding) = 0; + /** * Set whether a unit shape is moving or stationary. * @param tag tag of shape (must be valid and a unit shape)