<commit_before>#include "prefab_scene_data.h"

#include "halley/bytes/byte_serializer.h"
#include "halley/core/resources/resources.h"
#include "halley/support/logger.h"
#include "world.h"

using namespace Halley;

PrefabSceneData::PrefabSceneData(Prefab& prefab, std::shared_ptr<EntityFactory> factory, World& world, Resources& gameResources)
	: prefab(prefab)
	, factory(std::move(factory))
	, world(world)
	, gameResources(gameResources)
{
}

ISceneData::EntityData PrefabSceneData::getEntityData(const String& id)
{
	if (id.isEmpty()) {
		return EntityData(prefab.getRoot(), "");
	}
	
	const auto& [data, parentData] = findEntityAndParent(prefab.getRoot(), nullptr, id);
	if (!data) {
		throw Exception("Entity data not found for \"" + id + "\"", HalleyExceptions::Entity);
	}

	String parentId;
	if (parentData) {
		parentId = (*parentData)["uuid"].asString();
	}
	return EntityData(*data, parentId);
}

void PrefabSceneData::reloadEntity(const String& id)
{
	if (!id.isEmpty()) {
		reloadEntity(id, findEntity(prefab.getRoot(), id));
	}
}

void PrefabSceneData::reloadEntity(const String& id, ConfigNode* data)
{
	auto entity = world.findEntity(UUID(id));
	if (entity) {
		if (data) {
			// Update
			factory->updateEntityTree(*entity, *data);
		} else {
			// Destroy
			world.destroyEntity(entity.value());
		}
	} else {
		if (data) {
			// Create
			factory->createEntity(*data);
		}
	}
}

EntityTree PrefabSceneData::getEntityTree() const
{
	EntityTree root;
	const auto& rootNode = prefab.getRoot();
	if (rootNode.getType() == ConfigNodeType::Sequence) {
		for (auto& n: rootNode.asSequence()) {
			fillEntityTree(n, root.children.emplace_back());
		}
	} else {
		fillEntityTree(rootNode, root);
	}
	return root;
}

void PrefabSceneData::fillEntityTree(const ConfigNode& node, EntityTree& tree) const
{
	tree.entityId = node["uuid"].asString("");
	if (node.hasKey("prefab")) {
		const auto& prefabName = node["prefab"].asString();
		const auto& prefabNode = gameResources.get<Prefab>(prefabName)->getRoot();
		//tree.entityId = prefabNode["uuid"].asString("");
		tree.name = prefabNode["name"].asString("");
		tree.prefab = prefabName;
	} else {
		tree.name = node["name"].asString("Entity");
		if (node["children"].getType() == ConfigNodeType::Sequence) {
			const auto& seq = node["children"].asSequence();
			tree.children.reserve(seq.size());

			for (auto& childNode : seq) {
				tree.children.emplace_back();
				fillEntityTree(childNode, tree.children.back());
			}
		}
	}
}

void PrefabSceneData::reparentEntity(const String& entityId, const String& newParentId, int childIndex)
{
	Expects(childIndex >= 0);
	Expects(!entityId.isEmpty());
	
	auto [entityMoving, oldParent] = findEntityAndParent(prefab.getRoot(), nullptr, entityId);

	if (!entityMoving) {
		throw Exception("Entity not found: " + entityId, HalleyExceptions::Tools);
	}
	const String oldParentId = oldParent ? (*oldParent)["uuid"].asString() : "";

	// WARNING: ALL OF THESE OPERATIONS CAN INVALIDATE OLD POINTERS, DON'T KEEP REFERENCES
	if (newParentId == oldParentId) {
		moveChild(findChildListFor(newParentId), entityId, size_t(childIndex));
	} else {
		// Don't collapse into one sequence point! findChildListFor(newParentId) MUST execute after removeChild()!
		auto child = removeChild(findChildListFor(oldParentId), entityId);
		addChild(findChildListFor(newParentId), childIndex, std::move(child));
	}

	reloadEntity(oldParentId);
	reloadEntity(newParentId);
}

bool PrefabSceneData::isSingleRoot()
{
	return prefab.getRoot().getType() != ConfigNodeType::Sequence;
}

ConfigNode::SequenceType& PrefabSceneData::findChildListFor(const String& id)
{
	auto& root = prefab.getRoot();
	if (id.isEmpty()) {
		if (root.getType() == ConfigNodeType::Sequence) {
			return root.asSequence();
		} else {<commit_msg>Yet more bugs with reparenting.<commit_after>#include "prefab_scene_data.h"

#include "halley/bytes/byte_serializer.h"
#include "halley/core/resources/resources.h"
#include "halley/support/logger.h"
#include "world.h"

using namespace Halley;

PrefabSceneData::PrefabSceneData(Prefab& prefab, std::shared_ptr<EntityFactory> factory, World& world, Resources& gameResources)
	: prefab(prefab)
	, factory(std::move(factory))
	, world(world)
	, gameResources(gameResources)
{
}

ISceneData::EntityData PrefabSceneData::getEntityData(const String& id)
{
	if (id.isEmpty()) {
		return EntityData(prefab.getRoot(), "");
	}
	
	const auto& [data, parentData] = findEntityAndParent(prefab.getRoot(), nullptr, id);
	if (!data) {
		throw Exception("Entity data not found for \"" + id + "\"", HalleyExceptions::Entity);
	}

	String parentId;
	if (parentData) {
		parentId = (*parentData)["uuid"].asString();
	}
	return EntityData(*data, parentId);
}

void PrefabSceneData::reloadEntity(const String& id)
{
	if (!id.isEmpty()) {
		reloadEntity(id, findEntity(prefab.getRoot(), id));
		world.spawnPending();
	}
}

void PrefabSceneData::reloadEntity(const String& id, ConfigNode* data)
{
	auto entity = world.findEntity(UUID(id));
	if (entity) {
		if (data) {
			// Update
			factory->updateEntityTree(*entity, *data);
		} else {
			// Destroy
			world.destroyEntity(entity.value());
		}
	} else {
		if (data) {
			// Create
			factory->createEntity(*data);
		}
	}
}

EntityTree PrefabSceneData::getEntityTree() const
{
	EntityTree root;
	const auto& rootNode = prefab.getRoot();
	if (rootNode.getType() == ConfigNodeType::Sequence) {
		for (auto& n: rootNode.asSequence()) {
			fillEntityTree(n, root.children.emplace_back());
		}
	} else {
		fillEntityTree(rootNode, root);
	}
	return root;
}

void PrefabSceneData::fillEntityTree(const ConfigNode& node, EntityTree& tree) const
{
	tree.entityId = node["uuid"].asString("");
	if (node.hasKey("prefab")) {
		const auto& prefabName = node["prefab"].asString();
		const auto& prefabNode = gameResources.get<Prefab>(prefabName)->getRoot();
		//tree.entityId = prefabNode["uuid"].asString("");
		tree.name = prefabNode["name"].asString("");
		tree.prefab = prefabName;
	} else {
		tree.name = node["name"].asString("Entity");
		if (node["children"].getType() == ConfigNodeType::Sequence) {
			const auto& seq = node["children"].asSequence();
			tree.children.reserve(seq.size());

			for (auto& childNode : seq) {
				tree.children.emplace_back();
				fillEntityTree(childNode, tree.children.back());
			}
		}
	}
}

void PrefabSceneData::reparentEntity(const String& entityId, const String& newParentId, int childIndex)
{
	Expects(childIndex >= 0);
	Expects(!entityId.isEmpty());
	
	auto [entityMoving, oldParent] = findEntityAndParent(prefab.getRoot(), nullptr, entityId);

	if (!entityMoving) {
		throw Exception("Entity not found: " + entityId, HalleyExceptions::Tools);
	}
	const String oldParentId = oldParent ? (*oldParent)["uuid"].asString() : "";

	// WARNING: ALL OF THESE OPERATIONS CAN INVALIDATE OLD POINTERS, DON'T KEEP REFERENCES
	if (newParentId == oldParentId) {
		moveChild(findChildListFor(newParentId), entityId, size_t(childIndex)); // INVALIDATES REFERENCES
		reloadEntity(newParentId.isEmpty() ? entityId : newParentId);
	} else {
		// The order is very important here
		// Don't collapse into one sequence point! findChildListFor(newParentId) MUST execute after removeChild()!
		auto child = removeChild(findChildListFor(oldParentId), entityId); // INVALIDATES REFERENCES

		// Reload before proceeding, so it can delete from root if needed
		reloadEntity(oldParentId.isEmpty() ? entityId : oldParentId);

		// Add to new parent
		addChild(findChildListFor(newParentId), childIndex, std::move(child)); // INVALIDATES REFERENCES

		// Reload destination
		reloadEntity(newParentId.isEmpty() ? entityId : newParentId);
	}
}

bool PrefabSceneData::isSingleRoot()
{
	return prefab.getRoot().getType() != ConfigNodeType::Sequence;
}

ConfigNode::SequenceType& PrefabSceneData::findChildListFor(const String& id)
{
	auto& root = prefab.getRoot();
	if (id.isEmpty()) {
		if (root.getType() == ConfigNodeType::Sequence) {
			return root.asSequence();
		} else {
{'fraction_non_alphanumeric': 0.15394227164813196, 'fraction_numerical': 0.00024990628514307136, 'mean_word_length': 5.372611464968153, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '517579', 'n_tokens_mistral': 2823, 'n_tokens_neox': 2654, 'n_words': 537}