Skip to content

Commit

Permalink
feat(onesync/server): basic state bag write policies
Browse files Browse the repository at this point in the history
* Basic rejection logic for write-protected keys in player/entity state bags.
* Resync replicated state bags with the source client (on rejection).
* Native handler 'SET_STATE_BAG_KEY_POLICY' to allow setting write policies per key.
  • Loading branch information
tens0rfl0w committed Nov 1, 2023
1 parent 1bdfac8 commit dbdc89c
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 3 deletions.
10 changes: 10 additions & 0 deletions code/components/citizen-resources-core/include/StateBagComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ class CRC_EXPORT StateBagComponent : public fwRefCountable, public IAttached<Res
//
virtual std::shared_ptr<StateBag> GetStateBag(std::string_view id) = 0;

//
// Sets the write policy for a state bag key.
//
virtual void SetKeyPolicy(std::string_view key, bool allow = true) = 0;

//
// Gets the write policy for a state bag key.
//
virtual bool GetKeyPolicy(std::string_view key) = 0;

//
// Registers a state bag for the specified identifier. The pointer returned should be
// the *only* reference, every reference kept internally should be weak.
Expand Down
50 changes: 47 additions & 3 deletions code/components/citizen-resources-core/src/StateBagComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class StateBagComponentImpl : public StateBagComponent

virtual std::shared_ptr<StateBag> GetStateBag(std::string_view id) override;

virtual void SetKeyPolicy(std::string_view key, bool allow = true) override;

virtual bool GetKeyPolicy(std::string_view key) override;

virtual std::shared_ptr<StateBag> RegisterStateBag(std::string_view id, bool useParentTargets = false) override;

virtual void SetGameInterface(StateBagGameInterface* gi) override;
Expand Down Expand Up @@ -82,6 +86,9 @@ class StateBagComponentImpl : public StateBagComponent
std::unordered_map<std::string, std::weak_ptr<StateBagImpl>> m_stateBags;
std::shared_mutex m_mapMutex;

std::unordered_set<std::string> m_keyPolicies;
std::shared_mutex m_keyPolicyMutex;

// pre-created state bag stuff

// list of state bag prefixes
Expand Down Expand Up @@ -476,6 +483,31 @@ std::shared_ptr<StateBag> StateBagComponentImpl::GetStateBag(std::string_view id
return (bag != m_stateBags.end()) ? bag->second.lock() : nullptr;
}

bool StateBagComponentImpl::GetKeyPolicy(std::string_view key)
{
#ifdef IS_FXSERVER
std::shared_lock lock(m_keyPolicyMutex);
return m_keyPolicies.find(std::string{ key }) == m_keyPolicies.end();
#else
return true;
#endif
}

void StateBagComponentImpl::SetKeyPolicy(std::string_view key, bool allow)
{
#ifdef IS_FXSERVER
std::shared_lock lock(m_keyPolicyMutex);
if (allow)
{
m_keyPolicies.erase(std::string(key));
}
else
{
m_keyPolicies.insert(std::string(key));
}
#endif
}

void StateBagComponentImpl::RegisterTarget(int id)
{
bool isNew = false;
Expand Down Expand Up @@ -669,17 +701,29 @@ void StateBagComponentImpl::HandlePacket(int source, std::string_view dataRaw, s
if (bag)
{
auto bagRef = std::static_pointer_cast<StateBagImpl>(bag);
auto key = std::string_view{ keyBuffer.data(), keyBuffer.size() };
auto writePolicy = GetKeyPolicy(key);

// TODO: rate checks, policy checks
auto peer = bagRef->GetOwningPeer();
if (!peer.has_value() || source == *peer)
if ((!peer.has_value() || source == *peer) && writePolicy)
{
bagRef->SetKey(
source,
std::string_view{ keyBuffer.data(), keyBuffer.size() },
key,
std::string_view{ reinterpret_cast<char*>(data.data()), data.size() },
m_role == StateBagRole::Server);
}
}
#ifdef IS_FXSERVER
// if we're the server, trigger resend to keep the client in sync
else if (!writePolicy)
{
if (auto serverBag = GetStateBag(bagName); auto serverKey = serverBag->GetKey(key))
{
bagRef->SendKeyValue(source, key, serverKey.value());
}
}
#endif
}
else if(outBagNameName != nullptr)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,4 +396,15 @@ static InitFunction initFunction([] ()

sbac->OnStateBagChange.Disconnect(size_t(cookie));
});

fx::ScriptEngine::RegisterNativeHandler("SET_STATE_BAG_KEY_POLICY", [](fx::ScriptContext& context)
{
auto keyName = context.CheckArgument<const char*>(0);
auto writePolicy = context.GetArgument<bool>(1);

auto rm = fx::ResourceManager::GetCurrent();
auto sbac = rm->GetComponent<fx::StateBagComponent>();

sbac->SetKeyPolicy(keyName, writePolicy);
});
});
16 changes: 16 additions & 0 deletions ext/native-decls/SetStateBagKeyPolicy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
ns: CFX
apiset: server
---
## SET_STATE_BAG_KEY_POLICY

```c
void SET_STATE_BAG_KEY_POLICY(char* keyName, BOOL writePolicy);
```
Sets the write policy for a statebag key.
(This affects all player and entity statebags.)
## Parameters
* **keyName**: The key name to set the write policy for.
* **writePolicy**: Whether to allow clients to write to this key.

0 comments on commit dbdc89c

Please sign in to comment.