Skip to content

Commit

Permalink
Merge Fix logic dealing with setting state for trains (pr-2902)
Browse files Browse the repository at this point in the history
8957209 - tweak(server/state): fix logic dealing with setting state for trains
  • Loading branch information
prikolium-cfx committed Dec 25, 2024
2 parents b7186ce + 8957209 commit cf42c3e
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,6 @@ enum EntityOrphanMode : uint8_t
DeleteOnOwnerDisconnect = 1,
KeepEntity = 2,
};

struct SyncEntityState
{
using TData = std::variant<int, float, bool, std::string>;
Expand Down Expand Up @@ -1367,6 +1366,10 @@ class ServerGameState : public ServerGameStatePublic, public fx::IAttached<fx::S
void HandleGameStateAck(fx::ServerInstanceBase* instance, const fx::ClientSharedPtr& client, net::packet::ClientGameStateAck& buffer) override;

void GetFreeObjectIds(const fx::ClientSharedPtr& client, uint8_t numIds, std::vector<uint16_t>& freeIds);

#ifdef STATE_FIVE
void IterateTrainLink(const sync::SyncEntityPtr& train, std::function<bool(sync::SyncEntityPtr&)> fn, bool callOnInitialEntity = true);
#endif

void ReassignEntity(uint32_t entityHandle, const fx::ClientSharedPtr& targetClient, std::unique_lock<std::shared_mutex>&& lock = {});

Expand All @@ -1382,7 +1385,51 @@ class ServerGameState : public ServerGameStatePublic, public fx::IAttached<fx::S
void ReassignEntityInner(uint32_t entityHandle, const fx::ClientSharedPtr& targetClient, std::unique_lock<std::shared_mutex>&& lock = {});

public:
void DeleteEntity(const fx::sync::SyncEntityPtr& entity);

template<bool IgnoreTrainChecks = false>
void DeleteEntity(const fx::sync::SyncEntityPtr& entity)
{
if (entity->type == sync::NetObjEntityType::Player || !entity->syncTree)
{
return;
}

// can only be used on FiveM, RDR doesn't have its sync nodes filled out
#ifdef STATE_FIVE
// this will be ignored by DELETE_TRAIN so calling on any part of the train will delete the entire thing
if constexpr (!IgnoreTrainChecks)
{

if (entity->type == sync::NetObjEntityType::Train;
auto trainState = entity->syncTree->GetTrainState())
{
// don't allow the deletion of carriages until we can modify sync node data and overwrite the linked forward/linked backwards state
if (trainState->engineCarriage && trainState->engineCarriage != entity->handle)
{
return;
}
}
}
#endif

gscomms_execute_callback_on_sync_thread([=]()
{
#ifdef STATE_FIVE
if (entity->type == sync::NetObjEntityType::Train)
{
// recursively delete every part of the train
IterateTrainLink(entity, [=](fx::sync::SyncEntityPtr& train)
{
RemoveClone({}, train->handle);

return true;
});
return;
}
#endif
RemoveClone({}, entity->handle);
});
}

void ClearClientFromWorldGrid(const fx::ClientSharedPtr& targetClient);

Expand Down
153 changes: 82 additions & 71 deletions code/components/citizen-server-impl/src/state/ServerGameState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1166,34 +1166,16 @@ void ServerGameState::Tick(fx::ServerInstanceBase* instance)

if (auto engine = GetTrain(this, trainState->engineCarriage))
{
{
IterateTrainLink(entity, [&isRelevant, isRelevantViaPos](sync::SyncEntityPtr& train) {
float position[3];
engine->syncTree->GetPosition(position);
train->syncTree->GetPosition(position);

glm::vec3 entityPosition(position[0], position[1], position[2]);
if (isRelevantViaPos(engine, entityPosition))
{
isRelevant = true;
}
}

// if not via the engine, try the next-train chain
if (!isRelevant)
{
for (auto link = GetNextTrain(this, engine); link; link = GetNextTrain(this, link))
{
float position[3];
link->syncTree->GetPosition(position);

glm::vec3 entityPosition(position[0], position[1], position[2]);
isRelevant = isRelevantViaPos(train, entityPosition);

if (isRelevantViaPos(link, entityPosition))
{
isRelevant = true;
break;
}
}
}
// if we're not still relevant then we should keep going
return !isRelevant;
});
}
}
#endif
Expand Down Expand Up @@ -2644,6 +2626,65 @@ void ServerGameState::ReassignEntityInner(uint32_t entityHandle, const fx::Clien
m_objectIdsStolen.set(entityHandle);
}
}

#ifdef STATE_FIVE
/// <summary>
/// Takes the initialTrain and if its the engine train, iterates down the train link from the train, if it's not then it will try to get the engine
/// </summary>
/// <param name="initialTrain">The initial to start the iteration from, this will get the engine entity internally, if the engine doesn't exist it will early return as there's no valid part in the link to start from.</param>
/// <param name="fn">The function to call, if the function returns `true` it will keep iterating, if it returns `false` it will stop</param>
/// <param name="callOnInitialEntity">Whether the function should do the `fn` call on the initialTrain</param>
void ServerGameState::IterateTrainLink(const sync::SyncEntityPtr& initialTrain, std::function<bool(sync::SyncEntityPtr&)> fn, bool callOnInitialEntity)
{
// for most stuff we want to call on the intial entity
if (callOnInitialEntity)
{
if (!fn(const_cast<sync::SyncEntityPtr&>(initialTrain)))
{
return;
}
}

if (auto trainState = initialTrain->syncTree->GetTrainState())
{
auto recurseTrain = [=](const fx::sync::SyncEntityPtr& train)
{
for (auto link = GetNextTrain(this, train); link; link = GetNextTrain(this, link))
{
// this is expected to make sure that the initial train & the link trains are not called twice
// since this could lead to double locking the client mutex in ReassignEntity
// we also ignore the train sent via the call to `recurseTrain` as we should've called `fn` before here
if (link->handle == initialTrain->handle)
{
continue;
}

// if the function returns true then we should stop iterating
if (!fn(link))
{
return;
}
}
};

if (trainState->isEngine)
{
recurseTrain(initialTrain);
}
else if (trainState->engineCarriage && trainState->engineCarriage != initialTrain->handle)
{
if (auto engine = GetTrain(this, trainState->engineCarriage))
{
if (!fn(engine))
{
return;
}
recurseTrain(engine);
}
}
}
}
#endif

void ServerGameState::ReassignEntity(uint32_t entityHandle, const fx::ClientSharedPtr& targetClient, std::unique_lock<std::shared_mutex>&& lock)
{
Expand All @@ -2654,42 +2695,22 @@ void ServerGameState::ReassignEntity(uint32_t entityHandle, const fx::ClientShar
#ifdef STATE_FIVE
if (auto train = GetTrain(this, entityHandle))
{
// game code works as follows:
// -> if train isEngine, enumerate the entire list backwards and migrate that one along
// -> if not isEngine, migrate the engine
if (auto trainState = train->syncTree->GetTrainState())
{
auto reassignEngine = [this, &targetClient, entityHandle](const fx::sync::SyncEntityPtr& train)
{
for (auto link = GetNextTrain(this, train); link; link = GetNextTrain(this, link))
{
// this check should prevent the following two states:
// 1. double-locking clientMutex
// 2. reassigning the same entity twice
if (link->handle != entityHandle)
{
// we directly use ReassignEntityInner here to ensure no infinite recursion
ReassignEntityInner(link->handle, targetClient);
}
}
};

if (trainState->isEngine)
{
reassignEngine(train);
}
else if (trainState->engineCarriage && trainState->engineCarriage != entityHandle)
{
// reassign the engine carriage
ReassignEntityInner(trainState->engineCarriage, targetClient);

// get the engine and reassign based on that
if (auto engine = GetTrain(this, trainState->engineCarriage))
{
reassignEngine(engine);
}
}
}
// game code works as follows:
// -> if train isEngine, enumerate the entire list backwards and migrate that one along
// -> if not isEngine, migrate the engine


// This call expects link-handle != entityHandle
// This will prevent
// 1. double-locking clientMutex
// 2. reassigning the same entity twice
IterateTrainLink(train, [=](const fx::sync::SyncEntityPtr& link) {

// we directly use ReassignEntityInner here to ensure no infinite recursion
ReassignEntityInner(link->handle, targetClient);

return true;
}, false);
}
#endif
}
Expand Down Expand Up @@ -3141,7 +3162,7 @@ void ServerGameState::ProcessCloneRemove(const fx::ClientSharedPtr& client, rl::

return;
}


GS_LOG("%s: queueing remove (%d - %d)\n", __func__, objectId, uniqifier);
RemoveClone(client, objectId, uniqifier);
}
Expand Down Expand Up @@ -4399,16 +4420,6 @@ void ServerGameState::HandleGameStateAck(fx::ServerInstanceBase* instance, const
}
}

void ServerGameState::DeleteEntity(const fx::sync::SyncEntityPtr& entity)
{
if (entity->type != sync::NetObjEntityType::Player && entity->syncTree)
{
gscomms_execute_callback_on_sync_thread([=]()
{
RemoveClone({}, entity->handle);
});
}
}

void ServerGameState::SendPacket(int peer, net::packet::StateBagPacket& packet)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ void DisownEntityScript(const fx::sync::SyncEntityPtr& entity);

static void Init()
{

static auto IsEntityValid = [](const fx::sync::SyncEntityPtr& entity) {
// if we're deleting or finalizing our deletion then we don't want to be included in the list
return entity && !entity->deleting && !entity->finalizing;
};

auto makeEntityFunction = [](auto fn, uintptr_t defaultValue = 0)
{
return [=](fx::ScriptContext& context)
Expand All @@ -43,7 +49,7 @@ static void Init()

auto entity = gameState->GetEntity(id);

if (!entity)
if (!IsEntityValid(entity))
{
throw std::runtime_error(va("Tried to access invalid entity: %d", id));

Expand Down Expand Up @@ -131,7 +137,7 @@ static void Init()

auto entity = gameState->GetEntity(id);

if (!entity)
if (!IsEntityValid(entity))
{
context.SetResult(false);
return;
Expand Down Expand Up @@ -163,7 +169,7 @@ static void Init()

auto entity = gameState->GetEntity(id);

if (!entity || entity->finalizing || entity->deleting)
if (!IsEntityValid(entity))
{
context.SetResult(false);
return;
Expand Down Expand Up @@ -197,7 +203,7 @@ static void Init()

auto entity = gameState->GetEntity(0, id);

if (!entity)
if (!IsEntityValid(entity))
{
context.SetResult(0);
return;
Expand Down Expand Up @@ -253,19 +259,36 @@ static void Init()

auto entity = gameState->GetEntity(id);

if (!entity)
if (!IsEntityValid(entity))
{
throw std::runtime_error(va("Tried to access invalid entity: %d", id));
}

auto orphanMode = context.GetArgument<int>(1);
int rawOrphanMode = context.GetArgument<int>(1);

if (orphanMode < 0 || orphanMode > fx::sync::KeepEntity)
if (rawOrphanMode < 0 || rawOrphanMode > fx::sync::KeepEntity)
{
throw std::runtime_error(va("Tried to set entities (%d) orphan mode to an invalid orphan mode: %d", id, orphanMode));
throw std::runtime_error(va("Tried to set entities (%d) orphan mode to an invalid orphan mode: %d", id, rawOrphanMode));
}

entity->orphanMode = static_cast<fx::sync::EntityOrphanMode>(orphanMode);

fx::sync::EntityOrphanMode entityOrphanMode = static_cast<fx::sync::EntityOrphanMode>(rawOrphanMode);

#ifdef STATE_FIVE
if (entity->type == fx::sync::NetObjEntityType::Train)
{
// recursively apply orphan mode to all of the trains children/parents
gameState->IterateTrainLink(entity, [entityOrphanMode](fx::sync::SyncEntityPtr& train) {
train->orphanMode = entityOrphanMode;

return true;
});
}
else
#endif
{
entity->orphanMode = entityOrphanMode;
}

// if they set the orphan mode to `DeleteOnOwnerDisconnect` and the entity already doesn't have an owner then treat this as a `DELETE_ENTITY` call
if (entity->orphanMode == fx::sync::DeleteOnOwnerDisconnect && entity->firstOwnerDropped)
Expand Down Expand Up @@ -1080,11 +1103,6 @@ static void Init()
return result;
}));

static auto IsEntityValid = [](const fx::sync::SyncEntityPtr& entity) {
// if we're deleting or finalizing our deletion then we don't want to be included in the list
return entity && !entity->deleting && !entity->finalizing;
};

fx::ScriptEngine::RegisterNativeHandler("GET_GAME_POOL", [](fx::ScriptContext& context)
{
// get the current resource manager
Expand Down Expand Up @@ -1295,6 +1313,18 @@ static void Init()

return 0;
}));

fx::ScriptEngine::RegisterNativeHandler("DELETE_TRAIN", makeEntityFunction([](fx::ScriptContext& context, const fx::sync::SyncEntityPtr& entity)
{
auto resourceManager = fx::ResourceManager::GetCurrent();
auto instance = resourceManager->GetComponent<fx::ServerInstanceBaseRef>()->Get();
auto gameState = instance->GetComponent<fx::ServerGameState>();

// ignore the engine checks, this will recursively delete the entire train
gameState->DeleteEntity<true>(entity);

return 0;
}));

fx::ScriptEngine::RegisterNativeHandler("SET_ENTITY_AS_NO_LONGER_NEEDED", [](fx::ScriptContext& context)
{
Expand Down
2 changes: 2 additions & 0 deletions ext/native-decls/DeleteEntity.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ void DELETE_ENTITY(Entity entity);
Deletes the specified entity.
**NOTE**: For trains this will only work if called on the train engine, it will not work on its carriages.
## Parameters
* **entity**: The entity to delete.
Loading

0 comments on commit cf42c3e

Please sign in to comment.