mirror of
				https://github.com/NoelFB/tiny_link.git
				synced 2025-10-31 01:41:33 +08:00 
			
		
		
		
	very simple entity/component system
This commit is contained in:
		| @ -11,6 +11,7 @@ add_subdirectory(libs/blah) | ||||
| # add our source | ||||
| add_executable(game | ||||
| 	src/main.cpp | ||||
| 	src/world.cpp | ||||
| ) | ||||
|  | ||||
| # Reference blah | ||||
| @ -21,7 +22,5 @@ set(SDL2_DLL "" CACHE FILEPATH "SDL2 DLL Path") | ||||
| if (SDL2_ENABLED) | ||||
| 	add_custom_command( | ||||
| 		TARGET game POST_BUILD | ||||
| 		COMMAND ${CMAKE_COMMAND} -E copy | ||||
| 				${SDL2_DLL} | ||||
| 				$<TARGET_FILE_DIR:game>/SDL2.dll) | ||||
| 		COMMAND ${CMAKE_COMMAND} -E copy ${SDL2_DLL} $<TARGET_FILE_DIR:game>) | ||||
| endif() | ||||
							
								
								
									
										271
									
								
								src/world.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								src/world.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,271 @@ | ||||
| #include "world.h" | ||||
| #include <algorithm> | ||||
|  | ||||
| using namespace Blah; | ||||
| using namespace TL; | ||||
|  | ||||
| uint8_t Component::type() const | ||||
| { | ||||
| 	return m_type; | ||||
| } | ||||
|  | ||||
| Entity* Component::entity() | ||||
| { | ||||
| 	return m_entity; | ||||
| } | ||||
|  | ||||
| const Entity* Component::entity() const | ||||
| { | ||||
| 	return m_entity; | ||||
| } | ||||
|  | ||||
| World* Component::world() | ||||
| { | ||||
| 	return (m_entity ? m_entity->world() : nullptr); | ||||
| } | ||||
|  | ||||
| const World* Component::world() const | ||||
| { | ||||
| 	return (m_entity ? m_entity->world() : nullptr); | ||||
| } | ||||
|  | ||||
| Component* Component::prev() | ||||
| { | ||||
| 	return m_prev; | ||||
| } | ||||
|  | ||||
| const Component* Component::prev() const | ||||
| { | ||||
| 	return m_prev; | ||||
| } | ||||
|  | ||||
| Component* Component::next() | ||||
| { | ||||
| 	return m_next; | ||||
| } | ||||
|  | ||||
| const Component* Component::next() const | ||||
| { | ||||
| 	return m_next; | ||||
| } | ||||
|  | ||||
| void Component::destroy() | ||||
| { | ||||
| 	if (m_entity && m_entity->world()) | ||||
| 	{ | ||||
| 		m_entity->world()->destroy(this); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Component::awake() {} | ||||
| void Component::update() {} | ||||
| void Component::render(Blah::Batch& batch) {} | ||||
| void Component::destroyed() {} | ||||
|  | ||||
| World* Entity::world() | ||||
| { | ||||
| 	return m_world; | ||||
| } | ||||
|  | ||||
| const World* Entity::world() const | ||||
| { | ||||
| 	return m_world; | ||||
| } | ||||
|  | ||||
| Entity* Entity::prev() | ||||
| { | ||||
| 	return m_prev; | ||||
| } | ||||
|  | ||||
| const Entity* Entity::prev() const | ||||
| { | ||||
| 	return m_prev; | ||||
| } | ||||
|  | ||||
| Entity* Entity::next() | ||||
| { | ||||
| 	return m_next; | ||||
| } | ||||
|  | ||||
| const Entity* Entity::next() const | ||||
| { | ||||
| 	return m_next; | ||||
| } | ||||
|  | ||||
| Blah::Vector<Component*>& Entity::components() | ||||
| { | ||||
| 	return m_components; | ||||
| } | ||||
|  | ||||
| const Blah::Vector<Component*>& Entity::components() const | ||||
| { | ||||
| 	return m_components; | ||||
| } | ||||
|  | ||||
| World::~World() | ||||
| { | ||||
| 	// destroy all the entities | ||||
| 	while (m_alive.first) | ||||
| 		destroy_entity(m_alive.first); | ||||
|  | ||||
| 	// delete component instances | ||||
| 	for (int i = 0; i < Component::Types::count(); i++) | ||||
| 	{ | ||||
| 		Component* c = m_components_cache[i].first; | ||||
| 		while (c) | ||||
| 		{ | ||||
| 			Component* next = c->m_next; | ||||
| 			delete c; | ||||
| 			c = next; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// delete entity instances | ||||
| 	Entity* e = m_cache.first; | ||||
| 	while (e) | ||||
| 	{ | ||||
| 		Entity* next = e->m_next; | ||||
| 		delete e; | ||||
| 		e = next; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Entity* World::add_entity() | ||||
| { | ||||
| 	// create entity instance | ||||
| 	Entity* instance; | ||||
| 	if (m_cache.first) | ||||
| 	{ | ||||
| 		instance = m_cache.first; | ||||
| 		m_cache.remove(instance); | ||||
| 		*instance = Entity(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		instance = new Entity(); | ||||
| 	} | ||||
|  | ||||
| 	// add to list | ||||
| 	m_alive.insert(instance); | ||||
|  | ||||
| 	// assign | ||||
| 	instance->m_world = this; | ||||
|  | ||||
| 	// return new entity! | ||||
| 	return instance; | ||||
| } | ||||
|  | ||||
| Entity* World::first_entity() | ||||
| { | ||||
| 	return m_alive.first; | ||||
| } | ||||
|  | ||||
| const Entity* World::first_entity() const | ||||
| { | ||||
| 	return m_alive.first; | ||||
| } | ||||
|  | ||||
| Entity* World::last_entity() | ||||
| { | ||||
| 	return m_alive.last; | ||||
| } | ||||
|  | ||||
| const Entity* World::last_entity() const | ||||
| { | ||||
| 	return m_alive.last; | ||||
| } | ||||
|  | ||||
| void World::destroy_entity(Entity* entity) | ||||
| { | ||||
| 	if (entity && entity->m_world == this) | ||||
| 	{ | ||||
| 		// destroy components | ||||
| 		for (int i = entity->m_components.size() - 1; i >= 0; i--) | ||||
| 			destroy(entity->m_components[i]); | ||||
|  | ||||
| 		// remove ourselves from the list | ||||
| 		m_alive.remove(entity); | ||||
| 		m_cache.insert(entity); | ||||
|  | ||||
| 		// donezo | ||||
| 		entity->m_world = nullptr; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void World::destroy(Component* component) | ||||
| { | ||||
| 	if (component && component->m_entity && component->m_entity->m_world == this) | ||||
| 	{ | ||||
| 		auto type = component->m_type; | ||||
|  | ||||
| 		// mark destroyed | ||||
| 		component->destroyed(); | ||||
|  | ||||
| 		// remove from entity | ||||
| 		auto& list = component->m_entity->m_components; | ||||
| 		for (int i = list.size() - 1; i >= 0; i--) | ||||
| 		{ | ||||
| 			if (list[i] == component) | ||||
| 			{ | ||||
| 				list.erase(i); | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// remove from list | ||||
| 		m_components_alive[type].remove(component); | ||||
| 		m_components_cache[type].insert(component); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void World::update() | ||||
| { | ||||
| 	for (int i = 0; i < Component::Types::count(); i++) | ||||
| 	{ | ||||
| 		auto component = m_components_alive[i].first; | ||||
| 		while (component) | ||||
| 		{ | ||||
| 			auto next = component->m_next; | ||||
| 			if (component->active && component->m_entity->active) | ||||
| 				component->update(); | ||||
| 			component = next; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void World::render(Blah::Batch& batch) | ||||
| { | ||||
| 	// Notes: | ||||
| 	// In general this isn't a great way to render objects. | ||||
| 	// Every frame it has to rebuild the list and sort it. | ||||
| 	// A more ideal way would be to cache the visible list | ||||
| 	// and insert / remove objects as they update or change | ||||
| 	// their depth | ||||
|  | ||||
| 	// However, given the scope of this project, this is fine. | ||||
|  | ||||
| 	// assemble list | ||||
| 	for (int i = 0; i < Component::Types::count(); i++) | ||||
| 	{ | ||||
| 		auto component = m_components_alive[i].first; | ||||
| 		while (component) | ||||
| 		{ | ||||
| 			if (component->visible && component->m_entity->visible) | ||||
| 				m_visible.push_back(component); | ||||
| 			component = component->m_next; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// sort by depth | ||||
| 	std::sort(m_visible.begin(), m_visible.end(), [](const Component* a, const Component* b) | ||||
| 		{ | ||||
| 			return a->depth > b->depth; | ||||
| 		}); | ||||
|  | ||||
| 	// render them | ||||
| 	for (auto& it : m_visible) | ||||
| 		it->render(batch); | ||||
|  | ||||
| 	// clear list for the next time around | ||||
| 	m_visible.clear(); | ||||
| } | ||||
							
								
								
									
										309
									
								
								src/world.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								src/world.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,309 @@ | ||||
| #pragma once | ||||
| #include <blah.h> | ||||
|  | ||||
| namespace TL | ||||
| { | ||||
| 	class World; | ||||
| 	class Entity; | ||||
|  | ||||
| 	class Component | ||||
| 	{ | ||||
| 		friend class World; | ||||
| 		friend class Entity; | ||||
|  | ||||
| 	public: | ||||
| 		bool active = true; | ||||
| 		bool visible = true; | ||||
| 		int depth = 0; | ||||
|  | ||||
| 		uint8_t type() const; | ||||
|  | ||||
| 		Entity* entity(); | ||||
| 		const Entity* entity() const; | ||||
|  | ||||
| 		World* world(); | ||||
| 		const World* world() const; | ||||
|  | ||||
| 		Component* prev(); | ||||
| 		const Component* prev() const; | ||||
|  | ||||
| 		Component* next(); | ||||
| 		const Component* next() const; | ||||
|  | ||||
| 		template<class T> | ||||
| 		T* get(); | ||||
|  | ||||
| 		template<class T> | ||||
| 		const T* get() const; | ||||
|  | ||||
| 		void destroy(); | ||||
|  | ||||
| 		virtual void awake(); | ||||
| 		virtual void update(); | ||||
| 		virtual void render(Blah::Batch& batch); | ||||
| 		virtual void destroyed(); | ||||
|  | ||||
| 	private: | ||||
| 		uint8_t m_type = 0; | ||||
| 		Entity* m_entity = nullptr; | ||||
| 		Component* m_prev = nullptr; | ||||
| 		Component* m_next = nullptr; | ||||
|  | ||||
| 		class Types | ||||
| 		{ | ||||
| 		private: | ||||
| 			static inline uint8_t s_counter = 0; | ||||
|  | ||||
| 		public: | ||||
| 			static uint8_t count() { return s_counter; } | ||||
|  | ||||
| 			template<class T> | ||||
| 			static uint8_t id() | ||||
| 			{ | ||||
| 				static const uint8_t value = Types::s_counter++; | ||||
| 				return value; | ||||
| 			} | ||||
| 		}; | ||||
| 	}; | ||||
|  | ||||
| 	class Entity | ||||
| 	{ | ||||
| 		friend class World; | ||||
|  | ||||
| 	public: | ||||
| 		bool active = true; | ||||
| 		bool visible = true; | ||||
| 		Blah::Vec2 position; | ||||
|  | ||||
| 		World* world(); | ||||
| 		const World* world() const; | ||||
|  | ||||
| 		Entity* prev(); | ||||
| 		const Entity* prev() const; | ||||
|  | ||||
| 		Entity* next(); | ||||
| 		const Entity* next() const; | ||||
|  | ||||
| 		template<class T> | ||||
| 		T* add(T&& component = T()); | ||||
|  | ||||
| 		template<class T> | ||||
| 		T* get(); | ||||
|  | ||||
| 		template<class T> | ||||
| 		const T* get() const; | ||||
|  | ||||
| 		Blah::Vector<Component*>& components(); | ||||
| 		const Blah::Vector<Component*>& components() const; | ||||
|  | ||||
| 	private: | ||||
| 		Blah::Vector<Component*> m_components; | ||||
| 		World* m_world = nullptr; | ||||
| 		Entity* m_prev = nullptr; | ||||
| 		Entity* m_next = nullptr; | ||||
| 	}; | ||||
|  | ||||
| 	class World | ||||
| 	{ | ||||
| 	public: | ||||
| 		static constexpr int max_component_types = 256; | ||||
|  | ||||
| 		World() = default; | ||||
| 		World(const World&) = delete; | ||||
| 		World(World&&) = delete; | ||||
| 		World& operator=(const World&) = delete; | ||||
| 		World& operator=(World&&) = delete; | ||||
| 		~World(); | ||||
|  | ||||
| 		Entity* add_entity(); | ||||
|  | ||||
| 		Entity* first_entity(); | ||||
|  | ||||
| 		const Entity* first_entity() const; | ||||
|  | ||||
| 		Entity* last_entity(); | ||||
|  | ||||
| 		const Entity* last_entity() const; | ||||
|  | ||||
| 		void destroy_entity(Entity* entity); | ||||
|  | ||||
| 		template<class T> | ||||
| 		T* add(Entity* entity, T&& component = T()); | ||||
|  | ||||
| 		template<class T> | ||||
| 		T* first(); | ||||
|  | ||||
| 		template<class T> | ||||
| 		const T* first() const; | ||||
|  | ||||
| 		template<class T> | ||||
| 		T* last(); | ||||
|  | ||||
| 		template<class T> | ||||
| 		const T* last() const; | ||||
|  | ||||
| 		void destroy(Component* component); | ||||
|  | ||||
| 		void update(); | ||||
|  | ||||
| 		void render(Blah::Batch& batch); | ||||
|  | ||||
| 	private: | ||||
| 		template<class T> | ||||
| 		struct Pool | ||||
| 		{ | ||||
| 			T* first = nullptr; | ||||
| 			T* last = nullptr; | ||||
|  | ||||
| 			void insert(T* instance); | ||||
| 			void remove(T* instance); | ||||
| 		}; | ||||
|  | ||||
| 		Pool<Entity> m_cache; | ||||
| 		Pool<Entity> m_alive; | ||||
| 		Pool<Component> m_components_cache[max_component_types]; | ||||
| 		Pool<Component> m_components_alive[max_component_types]; | ||||
| 		Blah::Vector<Component*> m_visible; | ||||
| 	}; | ||||
|  | ||||
| 	template<class T> | ||||
| 	T* Component::get() | ||||
| 	{ | ||||
| 		BLAH_ASSERT(m_entity, "Component must be assigned to an Entity"); | ||||
| 		return entity->get<T>(); | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	const T* Component::get() const | ||||
| 	{ | ||||
| 		BLAH_ASSERT(m_entity, "Component must be assigned to an Entity"); | ||||
| 		return entity->get<T>(); | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	T* Entity::add(T&& component) | ||||
| 	{ | ||||
| 		BLAH_ASSERT(m_world, "Entity must be assigned to a World"); | ||||
| 		return m_world->add(this, std::forward<T>(component)); | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	T* Entity::get() | ||||
| 	{ | ||||
| 		BLAH_ASSERT(m_world, "Entity must be assigned to a World"); | ||||
| 		for (auto& it : m_components) | ||||
| 			if (it->type() == Component::Types::id<T>()) | ||||
| 				return (T*)it; | ||||
| 		return nullptr; | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	const T* Entity::get() const | ||||
| 	{ | ||||
| 		BLAH_ASSERT(m_world, "Entity must be assigned to a World"); | ||||
| 		for (auto& it : m_components) | ||||
| 			if (it->type() == Component::Types::id<T>()) | ||||
| 				return (T*)it; | ||||
| 		return nullptr; | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	T* World::add(Entity* entity, T&& component) | ||||
| 	{ | ||||
| 		BLAH_ASSERT(entity, "Entity cannot be null"); | ||||
| 		BLAH_ASSERT(entity->m_world == this, "Entity must be part of this world"); | ||||
|  | ||||
| 		// get the component type | ||||
| 		uint8_t type = Component::Types::id<T>(); | ||||
| 		auto& cache = m_components_cache[type]; | ||||
| 		auto& alive = m_components_alive[type]; | ||||
|  | ||||
| 		// instantiate a new instance | ||||
| 		T* instance; | ||||
| 		if (cache.first) | ||||
| 		{ | ||||
| 			instance = (T*)cache.first; | ||||
| 			cache.remove(instance); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			instance = new T(); | ||||
| 		} | ||||
|  | ||||
| 		// construct the new instance | ||||
| 		*instance = component; | ||||
| 		instance->m_type = type; | ||||
| 		instance->m_entity = entity; | ||||
|  | ||||
| 		// add it into the live components | ||||
| 		alive.insert(instance); | ||||
|  | ||||
| 		// add it to the entity | ||||
| 		entity->m_components.push_back(instance); | ||||
| 		 | ||||
| 		// and we're done! | ||||
| 		return instance; | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	T* World::first() | ||||
| 	{ | ||||
| 		uint8_t type = Component::Types::id<T>(); | ||||
| 		return m_components_alive[type].first; | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	const T* World::first() const | ||||
| 	{ | ||||
| 		uint8_t type = Component::Types::id<T>(); | ||||
| 		return m_components_alive[type].first; | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	T* World::last() | ||||
| 	{ | ||||
| 		uint8_t type = Component::Types::id<T>(); | ||||
| 		return m_components_alive[type].last; | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	const T* World::last() const | ||||
| 	{ | ||||
| 		uint8_t type = Component::Types::id<T>(); | ||||
| 		return m_components_alive[type].last; | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	void World::Pool<T>::insert(T* instance) | ||||
| 	{ | ||||
| 		if (last) | ||||
| 		{ | ||||
| 			last->m_next = instance; | ||||
| 			instance->m_prev = last; | ||||
| 			instance->m_next = nullptr; | ||||
| 			last = instance; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			first = last = instance; | ||||
| 			instance->m_prev = instance->m_next = nullptr; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	template<class T> | ||||
| 	void World::Pool<T>::remove(T* instance) | ||||
| 	{ | ||||
| 		if (instance->m_prev) | ||||
| 			instance->m_prev->m_next = instance->m_next; | ||||
| 		if (instance->m_next) | ||||
| 			instance->m_next->m_prev = instance->m_prev; | ||||
|  | ||||
| 		if (first == instance) | ||||
| 			first = instance->m_next; | ||||
| 		if (last == instance) | ||||
| 			last = instance->m_prev; | ||||
|  | ||||
| 		instance->m_next = nullptr; | ||||
| 		instance->m_prev = nullptr; | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user