1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
|
#pragma once
#include <list>
#include "inode.h"
#include "ieclass.h"
#include "ientity.h"
#include "iselection.h"
#include "iselectable.h"
#include "CollectiveSpawnargs.h"
namespace selection
{
/**
* Helper object used by the Entity Inspector to track all selected entities
* in the scene and keep the list of their spawnargs up to date.
*
* On update() it rescans the entity selection in the scene, triggering
* the contained CollectiveSpawnargs object to emit its signals. The signals
* are emitted such that the listening EntityInspector will show the
* correct set of keys: the ones with shared values will show just that,
* the keys with differing values will show a placeholder text instead.
*
* This instance will monitor the selected entities to dispatch all
* key value changes to the CollectiveSpawnargs.
* Selection updates will not take effect until update() is called, which
* ideally should happen in an idle processing loop after a selection change.
*
* This class keeps weak references to the scene::Nodes to not interfere with
* node destruction.
*/
class EntitySelection final :
public IEntitySelection
{
private:
class SpawnargTracker final :
public Entity::Observer
{
private:
CollectiveSpawnargs& _spawnargCollection;
Entity* _entity;
scene::INodeWeakPtr _node;
bool _destroySilently;
public:
SpawnargTracker(CollectiveSpawnargs& spawnargCollection, const scene::INodePtr& node) :
_spawnargCollection(spawnargCollection),
_entity(Node_getEntity(node)),
_node(node),
_destroySilently(false)
{
assert(_entity != nullptr);
_entity->attachObserver(this);
_spawnargCollection.onEntityAdded(_entity);
}
SpawnargTracker(const SpawnargTracker& other) = delete;
SpawnargTracker& operator=(const SpawnargTracker& other) = delete;
~SpawnargTracker()
{
if (!_destroySilently)
{
// Call the onEntityRemoved method instead of relying on the onKeyErase()
// invocations when detaching the observer. This allows us to keep some
// assumptions about entity count in the CollectiveSpawnargs::onKeyErase method
_spawnargCollection.onEntityRemoved(_entity);
}
// Clear the reference to disable the observer callbacks
auto entity = _entity;
_entity = nullptr;
if (!_node.expired())
{
// Detaching the observer won't do anything here anymore
entity->detachObserver(this);
}
}
scene::INodePtr getNode() const
{
return _node.lock();
}
void onKeyInsert(const std::string& key, EntityKeyValue& value) override
{
if (!_entity) return;
_spawnargCollection.onKeyInsert(_entity, key, value);
}
void onKeyChange(const std::string& key, const std::string& value) override
{
if (!_entity) return;
_spawnargCollection.onKeyChange(_entity, key, value);
}
void onKeyErase(const std::string& key, EntityKeyValue& value) override
{
if (!_entity) return;
_spawnargCollection.onKeyErase(_entity, key, value);
}
// Instruct the tracker to not fire any callbacks on destruction
void setDestroySilently(bool value)
{
_destroySilently = value;
}
};
std::list<SpawnargTracker> _trackedEntities;
CollectiveSpawnargs& _spawnargs;
public:
EntitySelection(CollectiveSpawnargs& spawnargs) :
_spawnargs(spawnargs)
{}
~EntitySelection()
{
_trackedEntities.clear();
}
bool empty() const override
{
return _trackedEntities.empty();
}
std::size_t size() const override
{
return _trackedEntities.size();
}
scene::INodePtr getSingleSelectedEntity() const
{
if (size() != 1)
{
throw std::runtime_error("Entity selection has count != 1");
}
return _trackedEntities.front().getNode();
}
// Returns non-empty reference if all selected entities share the same eclass
IEntityClassPtr getSingleSharedEntityClass()
{
try
{
IEntityClassPtr result;
foreachEntity([&](const IEntityNodePtr& entityNode)
{
auto eclass = entityNode->getEntity().getEntityClass();
if (!result)
{
result = std::move(eclass);
return;
}
if (result != eclass)
{
throw std::runtime_error("Non-unique eclass");
}
});
return result;
}
catch (const std::runtime_error&)
{
return {};
}
}
std::string getSharedKeyValue(const std::string& key, bool includeInherited) override
{
if (includeInherited)
{
std::set<std::string> values;
foreachEntity([&](const IEntityNodePtr& entity)
{
values.emplace(std::move(entity->getEntity().getKeyValue(key)));
});
return values.size() == 1 ? *values.begin() : std::string();
}
return _spawnargs.getSharedKeyValue(key);
}
void foreachEntity(const std::function<void(const IEntityNodePtr&)>& functor) override
{
for (auto& tracked : _trackedEntities)
{
auto node = tracked.getNode();
if (!node) continue;
auto entityNode = scene::node_cast<IEntityNode>(node);
assert(entityNode);
if (entityNode)
{
functor(entityNode);
}
}
}
// Rescan the selection system for entities to observe
void update()
{
// Assemble the set of currently selected entities
std::set<scene::INodePtr> selectedEntities;
GlobalSelectionSystem().foreachSelected([&](const scene::INodePtr& selected)
{
// Get the entity this node is attached to - a selected worldspawn brush
// will let the entity inspector interact with the world entity
auto entity = getEntityForNode(selected);
if (entity)
{
selectedEntities.emplace(std::move(entity));
}
});
// We take a shortcut in case a previous selection was fully cleared
if (selectedEntities.empty() && !_trackedEntities.empty())
{
// Prevent the spawnarg trackers from firing any callbacks on destruction
for (auto& tracked : _trackedEntities)
{
tracked.setDestroySilently(true);
}
_trackedEntities.clear();
// This will notify the collection that all entities are gone.
// It will fire remove events to properly clean up the client code
_spawnargs.onAllEntitiesRemoved();
return;
}
auto trackerCountChanged = false;
// Untrack all nodes that have been deleted or deselected in the meantime
for (auto tracked = _trackedEntities.begin(); tracked != _trackedEntities.end();)
{
auto node = tracked->getNode();
if (!node || selectedEntities.count(node) == 0)
{
// This entity is gone or no longer selected, remove the tracker entry
_trackedEntities.erase(tracked++);
trackerCountChanged = true;
continue;
}
// This entity is still selected, scratch it from the list
selectedEntities.erase(node);
++tracked;
}
// Entities that are still in the set at this point are untracked
for (auto& node : selectedEntities)
{
_trackedEntities.emplace_back(_spawnargs, node);
trackerCountChanged = true;
}
if (trackerCountChanged)
{
// Notify that the spawnarg collection about the changed selection set
_spawnargs.onEntityCountChanged();
}
}
private:
scene::INodePtr getEntityForNode(const scene::INodePtr& selected)
{
// The root node must not be selected (this can happen if Invert Selection is
// activated with an empty scene, or by direct selection in the entity list).
if (selected->isRoot()) return {};
if (Node_isEntity(selected))
{
return selected; // the node itself is an entity
}
// The node is not an entity, check its parent
auto selectedNodeParent = selected->getParent();
if (Node_isEntity(selectedNodeParent))
{
return selectedNodeParent;
}
return {}; // nothing of interest
}
};
}
|