Skip to content

Commit

Permalink
10430: Completes multithreading support for all opcodes and reentranc…
Browse files Browse the repository at this point in the history
…y, MINOR (#160)

Co-authored-by: Cade Mack <[email protected]>
  • Loading branch information
howsohazard and cademack authored Jun 27, 2024
1 parent 0c5ec9a commit e9c52f5
Show file tree
Hide file tree
Showing 11 changed files with 318 additions and 278 deletions.
1 change: 1 addition & 0 deletions src/Amalgam/AssetManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
//system headers:
#include <filesystem>
#include <string>
#include <tuple>

const std::string FILE_EXTENSION_AMLG_METADATA("mdam");
const std::string FILE_EXTENSION_AMALGAM("amlg");
Expand Down
12 changes: 12 additions & 0 deletions src/Amalgam/entity/Entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,18 @@ StringInternPool::StringID Entity::GetContainedEntityIdFromIndex(size_t entity_i
return contained_entities[entity_index]->GetIdStringId();
}

Entity *Entity::GetContainedEntityFromIndex(size_t entity_index)
{
if(!hasContainedEntities)
return nullptr;

if(entity_index >= entityRelationships.relationships->containedEntities.size())
return nullptr;

//look up the pointer by its index
return entityRelationships.relationships->containedEntities[entity_index];
}

void Entity::CreateQueryCaches()
{
EnsureHasContainedEntities();
Expand Down
180 changes: 22 additions & 158 deletions src/Amalgam/entity/Entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ class EntityReference
return entity;
}

protected:
EntityType *entity;
};

Expand Down Expand Up @@ -362,7 +361,7 @@ class Entity
/// write_listeners is optional, and if specified, will log the event
void RemoveContainedEntity(StringInternPool::StringID id, std::vector<EntityWriteListener *> *write_listeners = nullptr);

//returns the ID for a Entity that is contained by this Entity, null if it does not exist
//returns the Entity contained by this Entity for the given id, null if it does not exist
Entity *GetContainedEntity(StringInternPool::StringID id);

//returns the entity index for the given id
Expand All @@ -372,6 +371,9 @@ class Entity
//looks up the contained entity's string id based on its index in contained entities list
StringInternPool::StringID GetContainedEntityIdFromIndex(size_t entity_index);

//returns the Entity contained by this Entity for the given index, null if it does not exist
Entity *GetContainedEntityFromIndex(size_t entity_index);

//returns true if this entity has one or more contained entities
constexpr bool HasContainedEntities()
{
Expand Down Expand Up @@ -538,19 +540,36 @@ class Entity
//entities at the same level of depth
//returns the thread_local static variable entity[Read|Write]ReferenceBuffer, so results will be invalidated
//by subsequent calls
//if include_this_entity is true, it will include the entity in the references
template<typename EntityReferenceType>
inline EntityReferenceBufferReference<EntityReferenceType> GetAllDeeplyContainedEntityReferencesGroupedByDepth()
inline EntityReferenceBufferReference<EntityReferenceType> GetAllDeeplyContainedEntityReferencesGroupedByDepth(
bool include_this_entity = false)
{
EntityReferenceBufferReference<EntityReferenceType> erbr;
if constexpr(std::is_same<EntityReferenceType, EntityWriteReference>::value)
erbr = EntityReferenceBufferReference(entityWriteReferenceBuffer);
else
erbr = EntityReferenceBufferReference(entityReadReferenceBuffer);

if(include_this_entity)
{
if constexpr(std::is_same<EntityReferenceType, EntityWriteReference>::value)
entityWriteReferenceBuffer.emplace_back(this);
else
entityReadReferenceBuffer.emplace_back(this);
}

GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse<EntityReferenceType>();
return erbr;
}

//appends deply contained entity references to erbr
template<typename EntityReferenceType>
void AppendAllDeeplyContainedEntityReferencesGroupedByDepth(EntityReferenceBufferReference<EntityReferenceType> &erbr)
{
GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse<EntityReferenceType>();
}

//gets the current state of the random stream in string form
inline std::string GetRandomState()
{
Expand Down Expand Up @@ -684,168 +703,13 @@ class Entity
}

#ifdef MULTITHREAD_SUPPORT

//TODO 10975:
// * Remove most locks from Entity itself into Interpreter, etc.
// * Make sure there is a lock so the Entity can't be deleted with interpreters running
// * Apply the locking mechanisms below to all appropriate entity operations and use the appropriate Entity*Reference

//returns true if Entity a should be locked before b
static inline bool ShouldLockEntityABeforeB(Entity *a, Entity *b)
{
if(a == nullptr || b == nullptr)
return true;
return reinterpret_cast<intptr_t>(a) < reinterpret_cast<intptr_t>(b);
}

//Returns an appropriate lock object for operations on this Entity
//Note that it will only lock the Entity's immediate attributes, not contained entities, code, etc.
template<typename LockType>
inline LockType CreateEntityLock()
{
return LockType(mutex);
}

//Returns a vector of read locks for the whole entity and all contained Entities recursively
template<typename LockType>
inline Concurrency::MultipleLockBufferObject<LockType> CreateDeepEntityReadLocks(std::vector<LockType> &lock_buffer)
{
CreateDeepEntityLocksRecurse<LockType>(lock_buffer);
Concurrency::MultipleLockBufferObject<LockType> mbo(lock_buffer);
return mbo;
}

//recurively grabs read locks on the whole entity and everything contained
template<typename LockType>
void CreateDeepEntityLocksRecurse(std::vector<LockType> &lock_buffer)
{
//lock this one
lock_buffer.emplace_back(CreateEntityLock<LockType>());

//early out if done
if(!HasContainedEntities())
return;

auto &contained_entities = GetContainedEntities();

//need to store more
size_t num_contained = contained_entities.size();
lock_buffer.reserve(lock_buffer.size() + num_contained);

//collect and sort contained entities by address
std::vector<Entity *> contained_sorted;
contained_sorted.reserve(num_contained);

for(auto ce : contained_entities)
contained_sorted.push_back(ce);

std::sort(begin(contained_sorted), end(contained_sorted),
[](Entity *a, Entity *b)
{
return ShouldLockEntityABeforeB(a, b);
}
);

//lock all contained entities before proceeding further
for(auto e : contained_sorted)
lock_buffer.emplace_back(e->CreateEntityLock<LockType>());

for(auto e : contained_sorted)
e->CreateDeepEntityLocksRecurse<LockType>(lock_buffer);
}

//locks two entities
// locks will be released when the object is destructed
// makes sure there aren't deadlock conditions (circular wait) by consistently locking them in order of memory address
template<typename LockType>
class TwoEntityLock
{
public:
TwoEntityLock(Entity *a, Entity *b)
{
//if equal, but not null, just lock one
if(a == b && a != nullptr)
{
entityLockA = a->CreateEntityLock<LockType>();
return;
}

if(ShouldLockEntityABeforeB(a, b))
{
if(a != nullptr)
entityLockA = a->CreateEntityLock<LockType>();
if(b != nullptr)
entityLockB = b->CreateEntityLock<LockType>();
}
else
{
if(b != nullptr)
entityLockB = b->CreateEntityLock<LockType>();
if(a != nullptr)
entityLockA = a->CreateEntityLock<LockType>();
}
}

protected:
LockType entityLockA;
LockType entityLockB;
};

//Returns a vector of read locks for the whole entity and all contained Entities recursively
template<typename LockType>
static inline Concurrency::MultipleLockBufferObject<LockType> CreateDeepTwoEntityDeepLocks(Entity *a, Entity *b, std::vector<LockType> &lock_buffer)
{
CreateDeepTwoEntityDeepLocksRecurse(a, b, lock_buffer);
Concurrency::MultipleLockBufferObject<LockType> mbo(lock_buffer);
return mbo;
}

template<typename LockType>
static inline void CreateDeepTwoEntityDeepLocksRecurse(Entity *a, Entity *b, std::vector<LockType> &lock_buffer)
{
//handle the cases where one of the entities is nullptr by locking the one that isn't
if(a == nullptr)
{
if(b == nullptr)
return;

b->CreateDeepEntityLocksRecurse<LockType>(lock_buffer);
return;
}

if(b == nullptr)
{
a->CreateDeepEntityLocksRecurse<LockType>(lock_buffer);
return;
}

//both a and b are valid

//if one contains the other, just lock the outer one
if(a->DoesDeepContainEntity(b))
{
a->CreateDeepEntityLocksRecurse<LockType>(lock_buffer);
return;
}
if(b->DoesDeepContainEntity(a))
{
b->CreateDeepEntityLocksRecurse<LockType>(lock_buffer);
return;
}

//determine which to lock first
if(ShouldLockEntityABeforeB(a, b))
{
a->CreateDeepEntityLocksRecurse<LockType>(lock_buffer);
b->CreateDeepEntityLocksRecurse<LockType>(lock_buffer);
}
else
{
b->CreateDeepEntityLocksRecurse<LockType>(lock_buffer);
a->CreateDeepEntityLocksRecurse<LockType>(lock_buffer);
}
}

#endif

//nodes used for storing the entity and for all interpreters for this entity
Expand Down
11 changes: 3 additions & 8 deletions src/Amalgam/evaluablenode/EvaluableNodeManagement.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,9 @@ class EvaluableNodeReference
: value(value), unique(true)
{ }

constexpr EvaluableNodeReference(double value)
: unique(true)
{
if(FastIsNaN(value))
this->value = EvaluableNodeImmediateValueWithType();
else
this->value = EvaluableNodeImmediateValueWithType(value);
}
__forceinline EvaluableNodeReference(double value)
: value(value), unique(true)
{ }

__forceinline EvaluableNodeReference(StringInternPool::StringID string_id)
: value(string_intern_pool.CreateStringReference(string_id)), unique(true)
Expand Down
Loading

0 comments on commit e9c52f5

Please sign in to comment.