This commit is contained in:
Aria 2025-03-21 22:23:30 +11:00
commit 9c94d113d3
Signed by untrusted user who does not match committer: aria
GPG key ID: 19AB7AA462B8AB3B
10260 changed files with 1237388 additions and 0 deletions

View file

@ -0,0 +1,161 @@
#include "StarAccessControl.hpp"
#include "StarLogging.hpp"
#include "StarLexicalCast.hpp"
#include "StarFile.hpp"
#include "StarVariantExtra.hpp"
#include "StarAuthentication.hpp"
#include "StarAuthenticationKey.hpp"
#include "StarConfiguration.hpp"
#include "StarRoot.hpp"
namespace Star {
AccessControlClient::AccessControlClient(AccessControl* control) {
m_control = control;
m_rounds = 0;
m_intialized = false;
}
bool AccessControlClient::setAccountName(String const& account) {
// check if either the account has been banned
if (m_control->isBannedAccountName(account))
return false;
m_account = account;
auto sr = m_control->passwordSaltAndRounds(m_account);
m_salty = sr.first;
m_rounds = sr.second;
m_intialized = true;
return true;
}
bool AccessControlClient::setPlayerName(String const& playerName) {
if (m_control->isBannedPlayerName(playerName))
return false;
m_playerName = playerName;
return true;
}
String AccessControlClient::passwordSalt() {
if (!m_intialized)
throw StarException();
return m_salty;
}
int AccessControlClient::passwordRounds() {
if (!m_intialized)
throw StarException();
return m_rounds;
}
bool AccessControlClient::validate(String const& passwordHash) {
return m_control->validate(m_account, passwordHash, passwordRounds(), passwordSalt());
}
String AccessControlClient::processCommand(String const& command, StringList const& arguments) {
return m_control->processCommand(m_account, command, arguments);
}
AccessControl::AccessControl(String const& storageDirectory, UniverseServer* universeServer) {
m_universeServer = universeServer;
m_storageDirectory = storageDirectory;
load();
}
void AccessControl::load() {
if (!File::isFile(m_storageDirectory + "/accesscontrol.config")) {
Logger::warn("AccessControl: Failed to read accesscontrol file, creating a new file");
VariantMap vm;
vm.add("accounts", VariantMap());
vm.add("bannedAddresses", VariantList());
vm.add("bannedAccountNames", VariantList());
vm.add("bannedPlayerNames", VariantList());
{
MutexLocker locked(m_mutex);
m_state = vm;
}
save();
}
Variant newState = Variant::parseJson(File::readFileString(m_storageDirectory+"/accesscontrol.config"));
MutexLocker locked(m_mutex);
m_state = newState;
m_bannedAddresses = variantToStringSet(m_state.get("bannedAddresses"));
m_bannedAccountNames = variantToStringSet(m_state.get("bannedAccountNames"));
m_bannedPlayerNames = variantToStringSet(m_state.get("bannedPlayerNames"));
}
void AccessControl::save() {
MutexLocker locked(m_mutex);
File::overwriteFileWithRename(m_state.repr(1, true), m_storageDirectory+"/accesscontrol.config", ".new");
}
bool AccessControl::isBannedAddress(String const& address) {
MutexLocker locked(m_mutex);
return m_bannedAddresses.contains(address);
}
bool AccessControl::isBannedAccountName(String const& account) {
MutexLocker locked(m_mutex);
return m_bannedAccountNames.contains(account);
}
bool AccessControl::isBannedPlayerName(String const& playerName) {
MutexLocker locked(m_mutex);
return m_bannedPlayerNames.contains(playerName);
}
AccessControlClientPtr AccessControl::clientConnected() {
return make_shared<AccessControlClient>(this);
}
pair<String, int> AccessControl::passwordSaltAndRounds(String const& account) {
_unused(account);
return { Star::Auth::generateKey(), Root::singleton().configuration()->get("bcryptRounds").toInt()};
}
Variant AccessControl::accountValues(String const& account) {
MutexLocker locked(m_mutex);
if (m_state.get("accounts").contains(account))
return m_state.get("accounts").get(account);
return {};
}
bool AccessControl::validate(String const& account, String const& passwordHash, int rounds, String const& passwordSalt) {
if (account.empty()) {
for (auto password : Root::singleton().configuration()->get("serverPasswords").toList()) {
// forward bcrypt validate, kind of cheating, normally you'd store the hashed variants
if (Star::Auth::bcryptValidate(password.toString(), passwordSalt, passwordHash, rounds)) {
return true;
}
}
}
else {
auto accountSalt = account + passwordSalt;
Variant accountEntry = accountValues(account);
if (!accountEntry.isNull()) {
auto password = accountEntry.getString("password");
// forward bcrypt validate, kind of cheating, normally you'd store the hashed variants
if (Star::Auth::bcryptValidate(password, accountSalt, passwordHash, rounds)) {
return true;
}
}
}
return false;
}
String AccessControl::processCommand(String const& account, String const& command, StringList const& arguments) {
_unused(account);
_unused(arguments);
// ac-help
// ac-kick
// ac-banhard
// ac-banaccount
// ac-banip
// ac-players
return strf("Command '%' not recognized", command);
}
}

View file

@ -0,0 +1,70 @@
#ifndef STAR_ACCESS_CONTROL_HPP
#define STAR_ACCESS_CONTROL_HPP
#include "StarVariant.hpp"
#include "StarGameTypes.hpp"
#include "StarThread.hpp"
#include "StarSet.hpp"
namespace Star {
STAR_CLASS(UniverseServer);
STAR_CLASS(AccessControlClient);
STAR_CLASS(AccessControl);
class AccessControlClient {
public:
AccessControlClient(AccessControl* control);
bool setAccountName(String const& account);
bool setPlayerName(String const& playerName);
String passwordSalt();
int passwordRounds();
bool validate(String const& passwordHash);
String processCommand(String const& command, StringList const& arguments);
private:
AccessControl* m_control;
String m_account;
String m_playerName;
String m_salty;
int m_rounds;
bool m_intialized;
};
class AccessControl {
public:
AccessControl(String const& storageDirectory, UniverseServer* universeServer);
pair<String, int> passwordSaltAndRounds(String const& account);
bool validate(String const& account, String const& passwordHash, int rounds, String const& passwordSalt);
bool isBannedAddress(String const& connectionString);
bool isBannedAccountName(String const& account);
bool isBannedPlayerName(String const& playerName);
Variant accountValues(String const& account);
AccessControlClientPtr clientConnected();
String processCommand(String const& account, String const& command, StringList const& arguments);
private:
UniverseServer* m_universeServer;
String m_storageDirectory;
Variant m_state;
Mutex m_mutex;
Set<String> m_bannedAddresses;
Set<String> m_bannedAccountNames;
Set<String> m_bannedPlayerNames;
void load();
void save();
};
}
#endif

View file

@ -0,0 +1,270 @@
#include "StarAnimation.hpp"
namespace Star {
AnimationControl::~AnimationControl() { }
AnimationState::~AnimationState() { }
namespace Animation {
class Frame;
typedef std::shared_ptr<Frame> FramePtr;
class Rule;
typedef std::shared_ptr<Rule> RulePtr;
class Frame {
public:
virtual ~Frame() { }
virtual void apply(AnimationControlPtr controller) = 0;
static FramePtr construct(Variant const& config);
};
class StopSound: public Frame {
public:
StopSound(Variant const& config) {
}
virtual void apply(AnimationControlPtr controller) {
controller->stopSound();
}
};
class PlaySound: public Frame {
public:
PlaySound(Variant const& config) {
m_sound = config.getString(1);
}
virtual void apply(AnimationControlPtr controller) {
controller->playSound(m_sound);
}
private:
String m_sound;
};
class GraphicsFrame: public Frame {
public:
GraphicsFrame(Variant const& config) {
m_frame = config.getString(1);
}
virtual void apply(AnimationControlPtr controller) {
controller->setGraphic(m_frame);
}
private:
String m_frame;
};
class WireFrame: public Frame {
public:
WireFrame(Variant const& config) {
m_wire = config.getInt(1);
m_level = config.getBool(2);
}
virtual void apply(AnimationControlPtr controller) {
controller->setWireLevel(m_wire, m_level);
}
private:
int m_wire;
bool m_level;
};
class ChildTransitionFrame: public Frame {
public:
ChildTransitionFrame(Variant const& config) {
m_child = config.getString(1);
m_state = config.getString(2);
}
virtual void apply(AnimationControlPtr controller) {
controller->requestChildTransition(m_child, m_state);
}
private:
String m_child;
String m_state;
};
FramePtr Frame::construct(Variant const& config) {
String code;
VariantList arguments;
if (config.type() == Variant::Type::LIST) {
arguments = config.list();
code = config.getString(0);
}
else
{
code = "graphic";
arguments = {code, config};
}
if (code == "play")
return make_shared < PlaySound > (arguments);
if (code == "stopSound")
return make_shared < StopSound > (arguments);
if (code == "graphic")
return make_shared < GraphicsFrame > (arguments);
if (code == "wire")
return make_shared < WireFrame > (arguments);
if (code == "transition")
return make_shared < ChildTransitionFrame > (arguments);
throw StarException("Unknown animation frame kind '" + code + "'");
return {};
}
class Rule {
public:
virtual ~Rule() { }
virtual bool check(AnimationStatePtr state) = 0;
static RulePtr construct(String const& transition, Variant const& config);
};
class RequestedRule: public Rule {
public:
RequestedRule(Variant const& config);
virtual bool check(AnimationStatePtr state);
private:
String m_state;
};
RequestedRule::RequestedRule(Variant const& config) {
m_state = config.getString(1);
}
bool RequestedRule::check(AnimationStatePtr state) {
if (!state->hasRequestedFrame())
return false;
return state->requestedFrame() == m_state;
}
class CompletedRule: public Rule {
public:
CompletedRule(Variant const& config) {
}
virtual bool check(AnimationStatePtr state) {
return state->sequenceCompleted();
}
};
class StateRule: public Rule {
public:
StateRule(Variant const& config) {
m_child = config.getString(1);
m_state = config.getString(2);
}
virtual bool check(AnimationStatePtr state) {
return state->childState(m_child)->currentFrame() == m_state;
}
private:
String m_child;
String m_state;
};
RulePtr Rule::construct(String const& transition, Variant const& config) {
String ruleName;
VariantList arguments;
if (config.type() == Variant::Type::LIST) {
arguments = config.list();
ruleName = config.getString(0);
}
else {
ruleName = config.toString();
arguments = {ruleName, transition};
}
if (ruleName == "requested")
return make_shared < RequestedRule > (arguments);
else if (ruleName == "completed")
return make_shared < CompletedRule > (arguments);
else if (ruleName == "state")
return make_shared < StateRule > (arguments);
throw StarException("Unknown animation transition rule '" + ruleName + "'");
return {};
}
class Transition {
public:
Transition(Variant const& config);
String target;
List<RulePtr> rules;
};
Transition::Transition(Variant const& config) {
auto list = config.list();
target = config.getString(0);
for (size_t i = 1; i < list.size(); i++)
rules.append(Rule::construct(target, list[i]));
}
}
class AnimationSequence {
public:
AnimationSequence(Variant const& config);
void step(float timeStep);
private:
List<Animation::FramePtr> m_frames;
List<float> m_frameDuration;
List<Animation::Transition> m_transitions;
};
AnimationSequence::AnimationSequence(Variant const& config) {
for (auto frame : config.getList(0))
m_frames.append(Animation::Frame::construct(frame));
for (auto transition : config.getList(1))
m_transitions.append(Animation::Transition(transition));
if (config.size() == 3)
{
if (config.list()[2].type() != Variant::Type::LIST) {
for (size_t i = 0; i < m_frames.size(); i++)
m_frameDuration.append(config.getFloat(2));
}
else {
for (auto duration : config.getList(2))
m_frameDuration.append(duration.toFloat());
}
}
}
void AnimationSequence::step(float timeStep) {
//tooodles
}
class AnimationDefinition {
public:
AnimationDefinition(Variant const& config);
private:
Map<String, AnimationSequencePtr> m_sequnces;
Map<String, AnimationDefinitionPtr> m_nestedDefinitions;
};
AnimationDefinition::AnimationDefinition(Variant const& config) {
for (auto nested : config.getMap("nested")) {
m_nestedDefinitions.add(nested.first, make_shared < AnimationDefinition > (nested.second));
}
for (auto sequence : config.getMap("sequences")) {
m_sequnces.add(sequence.first, make_shared < AnimationSequence > (sequence.second));
}
}
AnimationDriver::AnimationDriver(AnimationControlPtr control, AnimationStatePtr state, Variant const& config, AnimationDriverMode mode) {
m_definition = make_shared < AnimationDefinition > (config);
m_control = control;
m_state = state;
m_mode = mode;
}
void AnimationDriver::step(float timeStep) {
/// 1 magic 2 ... 3 profit
}
}

View file

@ -0,0 +1,68 @@
#ifndef STAR_ANIMATION_HPP
#define STAR_ANIMATION_HPP
#include "StarVariant.hpp"
#include "StarColor.hpp"
#include "StarSet.hpp"
#include "StarAudio.hpp"
namespace Star {
class AnimationControl;
typedef std::shared_ptr<AnimationControl> AnimationControlPtr;
class AnimationState;
typedef std::shared_ptr<AnimationState> AnimationStatePtr;
class AnimationDefinition;
typedef std::shared_ptr<AnimationDefinition> AnimationDefinitionPtr;
class AnimationDriver;
typedef std::shared_ptr<AnimationDriver> AnimationDriverPtr;
class AnimationSequence;
typedef std::shared_ptr<AnimationSequence> AnimationSequencePtr;
class AnimationControl {
public:
virtual ~AnimationControl();
virtual void stopSound() = 0;
virtual void playSound(String const& resource) = 0;
virtual void setGraphic(String const& resource) = 0;
virtual void setWireLevel(int wire, bool level) = 0;
virtual void requestChildTransition(String const& child, String const& state) = 0;
virtual void bind(AnimationDefinitionPtr definition) = 0;
};
class AnimationState {
public:
virtual ~AnimationState();
virtual String currentFrame() = 0;
virtual int frameSequence() = 0;
virtual bool sequenceCompleted() = 0;
virtual bool soundPlaying() = 0;
virtual bool hasRequestedFrame() = 0;
virtual String requestedFrame() = 0;
virtual bool getWireLevel(int wire) = 0;
virtual AnimationStatePtr childState(String const& child) = 0;
virtual void bind(AnimationDefinitionPtr definition) = 0;
};
enum class AnimationDriverMode {
Server,
Client
};
class AnimationDriver {
public:
AnimationDriver(AnimationControlPtr control, AnimationStatePtr state, Variant const& config, AnimationDriverMode mode);
void step(float timeStep);
private:
AnimationDefinitionPtr m_definition;
AnimationControlPtr m_control;
AnimationStatePtr m_state;
AnimationDriverMode m_mode;
};
}
#endif

View file

@ -0,0 +1,290 @@
#include "StarAuthentication.hpp"
#include "StarRoot.hpp"
#include "StarConfiguration.hpp"
#include "StarClientAuthentication.hpp"
#include "StarAuthenticationConnection.hpp"
#include "StarAuthenticationService.hpp"
#include "StarLogging.hpp"
#include "StarFile.hpp"
using namespace Star::Auth;
namespace Star {
class AuthenticationImpl {
public:
AuthenticationImpl(Configuration const* configuration) {
m_rootKey = make_shared<Key>();
m_clientKey = make_shared<Key>();
m_rootKey->loadPublicKey(configuration->getString("rootKey"));
m_authenticator = make_shared<ClientAuthentication>(m_rootKey);
m_connector = make_shared<AuthenticationConnection>(configuration->getString("authHostname"), configuration->getInt("authPort"));
m_claimFile = Root::singleton().toStoragePath(configuration->getString("claimFile"));
m_hasClaim = false;
m_lazyLoadClaim = true;
m_remoteValidated = false;
}
bool validateClaim(bool remoteValidate) {
if (m_lazyLoadClaim)
loadClaim();
if (validClaim()) {
if (remoteValidate && !m_remoteValidated) {
remoteValidateClaim();
}
}
if (!m_hasClaim) {
deleteClaim();
}
return m_hasClaim;
}
Variant claim() {
if (!m_hasClaim)
throw StarException("No valid claim.");
return m_claim;
}
String decrypt(String const& message) {
if (!m_hasClaim)
throw StarException("No valid claim.");
return m_clientKey->decryptMessage(message);
}
String sign(String const& message) {
if (!m_hasClaim)
throw StarException("No valid claim.");
return m_clientKey->signMessage(message);
}
bool logon(String const& username, String const& passwordHash) {
m_lazyLoadClaim = false;
m_hasClaim = false;
m_remoteValidated = false;
try {
Variant authPubKeyRequest = VariantMap{
{"command", "getAuthKey"}
};
auto authSignatureString = m_connector->query(authPubKeyRequest.repr(0, true));
Variant authSignature = Variant::parse(authSignatureString);
if (!AuthenticationService::validateAuthSignature(*m_rootKey, authSignature))
throw StarException("Auth server key failure.");
m_clientKey->regenerate();
auto authsvrRequest = m_authenticator->authsvrRequest(username, passwordHash, authSignatureString, *m_clientKey);
Variant authorizeClientRequest = VariantMap{
{"command", "authorizeClient"},
{"body", authsvrRequest}
};
auto authsvrResponse = m_connector->query(authorizeClientRequest.repr(0, true));
auto logonResult = m_authenticator->authsvrResponse(authsvrResponse, *m_clientKey);
if (!AuthenticationService::validateClientClaim(*m_rootKey, logonResult))
throw StarException("Logon failure.");
m_claim = logonResult;
m_hasClaim = true;
m_remoteValidated = true;
saveClaim();
} catch (std::exception const& e) {
Logger::error("Exception while validating claim. %s", e.what());
}
return m_hasClaim;
}
bool remoteValidateClaim() {
if (!m_hasClaim)
return m_hasClaim;
try {
Variant authPubKeyRequest = VariantMap{
{"command", "getAuthKey"}
};
auto authSignatureString = m_connector->query(authPubKeyRequest.repr(0, true));
Variant authSignature = Variant::parse(authSignatureString);
if (!AuthenticationService::validateAuthSignature(*m_rootKey, authSignature))
throw StarException("Auth server key failure.");
auto authsvrRequest = m_authenticator->authsvrValidateRequest(m_claim, authSignatureString, *m_clientKey);
Variant authorizeClientRequest = VariantMap{
{"command", "validateClient"},
{"body", authsvrRequest}
};
auto authsvrResponse = m_connector->query(authorizeClientRequest.repr(0, true));
auto validateResult = m_authenticator->authsvrValidateResponse(authsvrResponse, *m_clientKey);
if (!validateResult) {
m_hasClaim = false;
m_remoteValidated = false;
}
} catch (std::exception const& e) {
// validation failure will not cause an exception btw
Logger::error("Exception while validating claim. %s", e.what());
}
return m_hasClaim;
}
bool validClaim() {
if (!m_hasClaim)
return false;
m_hasClaim = AuthenticationService::validateClientClaim(*m_rootKey, m_claim);
if (!m_hasClaim)
m_remoteValidated = false;
return m_hasClaim;
}
void loadClaim() {
m_lazyLoadClaim = false;
try {
m_hasClaim = false;
m_remoteValidated = false;
if (m_claimFile.empty())
return;
if (!File::isFile(m_claimFile))
return;
VariantMap file = Variant::parseJson(File::readFileString(m_claimFile)).toMap();
m_claim = file["claim"];
m_hasClaim = true;
m_clientKey->loadPrivateKey(file["key"].toString());
m_hasClaim = validClaim();
} catch (std::exception const& e) {
Logger::error("Exception while loading claim. %s", e.what());
}
if (!m_hasClaim) {
deleteClaim();
}
}
void saveClaim() {
m_lazyLoadClaim = false;
m_hasClaim = validClaim();
if (!m_hasClaim) {
deleteClaim();
return;
}
if (m_claimFile.empty())
return;
try {
VariantMap file;
file.insert("key", m_clientKey->privateKey());
file.insert("claim", m_claim);
File::overwriteFileWithRename(Variant(file).toJson(1), m_claimFile);
} catch (std::exception const& e) {
Logger::error("Exception while saving claim. %s", e.what());
}
}
void deleteClaim() {
m_lazyLoadClaim = false;
m_hasClaim = false;
m_remoteValidated = false;
m_claim = {};
try {
if (m_claimFile.empty())
return;
if (File::isFile(m_claimFile)) {
if (File::isFile(m_claimFile + ".old"))
File::remove(m_claimFile + ".old");
File::rename(m_claimFile, m_claimFile + ".old");
File::remove(m_claimFile + ".old");
}
} catch (std::exception const& e) {
Logger::error("Exception while removing claim. %s", e.what());
}
}
shared_ptr<ClientAuthentication> m_authenticator;
shared_ptr<AuthenticationConnection> m_connector;
String m_claimFile;
bool m_lazyLoadClaim;
bool m_hasClaim;
bool m_remoteValidated;
Variant m_claim;
shared_ptr<Key> m_rootKey;
shared_ptr<Key> m_clientKey;
};
class SharedClaimImpl: public SharedClaim {
public:
SharedClaimImpl(AuthenticationImpl* parent, Variant const& claim) {
m_parent = parent;
m_claim = claim;
m_clientKey = make_shared<Key>();
m_clientKey->loadPublicKey(m_claim.get("claim").get("metadata").get("clientPublicKey").toString());
}
bool validateClaim() {
return AuthenticationService::validateClientClaim(*(m_parent->m_rootKey), m_claim);
}
Variant claim() {
return m_claim;
}
String encrypt(String const& message) {
return m_clientKey->encryptMessage(message);
}
bool verify(String const& message, String const& signature) {
return m_clientKey->verifyMessage(message, signature);
}
String username() {
return m_claim.get("claim").get("metadata").getString("username");
}
AuthenticationImpl* m_parent;
Variant m_claim;
shared_ptr<Key> m_clientKey;
};
void Authentication::load() {
MutexLocker locker(m_mutex);
if (!m_impl)
m_impl = make_shared<AuthenticationImpl>(Root::singleton().configuration());
}
bool Authentication::validateClaim(bool remoteValidate) {
if (remoteValidate) {
auto remotevalidationimpl = make_shared<AuthenticationImpl>(Root::singleton().configuration());
bool result = remotevalidationimpl->validateClaim(true);
{
MutexLocker locker(m_mutex);
m_impl = remotevalidationimpl;
}
return result;
} else {
MutexLocker locker(m_mutex);
return m_impl->validateClaim(false);
}
}
bool Authentication::logon(String const& username, String const& passwordHash) {
MutexLocker locker(m_mutex);
return m_impl->logon(username, passwordHash);
}
Variant Authentication::claim() {
MutexLocker locker(m_mutex);
return m_impl->claim();
}
String Authentication::decrypt(String const& message) {
MutexLocker locker(m_mutex);
return m_impl->decrypt(message);
}
String Authentication::sign(String const& message) {
MutexLocker locker(m_mutex);
return m_impl->sign(message);
}
SharedClaimPtr Authentication::sharedClaim(Variant const& data) {
MutexLocker locker(m_mutex);
if (!m_impl)
throw StarException("Invalid state");
return make_shared<SharedClaimImpl>(m_impl.get(), data);
}
}

View file

@ -0,0 +1,46 @@
#ifndef STAR_AUTHENTICATION_HPP
#define STAR_AUTHENTICATION_HPP
#include "StarVariant.hpp"
#include "StarThread.hpp"
namespace Star {
STAR_CLASS(AuthenticationImpl);
STAR_CLASS(SharedClaim);
STAR_CLASS(Authentication);
class SharedClaim {
public:
virtual ~SharedClaim() {};
virtual bool validateClaim() = 0;
virtual Variant claim() = 0;
virtual String encrypt(String const& message) = 0;
virtual bool verify(String const& message, String const& signature) = 0;
virtual String username() = 0;
};
class Authentication {
public:
void load();
bool validateClaim(bool remoteValidate);
bool logon(String const& username, String const& passwordHash);
Variant claim();
String decrypt(String const& message);
String sign(String const& message);
SharedClaimPtr sharedClaim(Variant const& data);
private:
AuthenticationImplPtr m_impl;
Mutex m_mutex;
};
}
#endif

View file

@ -0,0 +1,64 @@
#ifndef STAR_CIRCLE_HPP
#define STAR_CIRCLE_HPP
#include "StarVector.hpp"
#include "StarLine.hpp"
namespace Star {
template<typename T>
class Circle {
public:
typedef Vector<T, 2> Vec2T;
typedef Line<T, 2> LineT;
Circle();
Circle(Vec2T const& pos, T radius);
bool intersects(Vec2T const& point);
Vec2T pointAtAngle(T angle);
T angleNearestTo(Vec2T const& point);
Vec2T pointNearestTo(Vec2T const& point);
private:
T m_radius;
Vec2T m_position;
};
template<typename T>
Circle<T>::Circle()
: m_radius(0) { }
template<typename T>
Circle<T>::Circle(Vec2T const& pos, T radius)
: m_radius(radius),
m_position(pos) { }
template<typename T>
bool Circle<T>::intersects(Vec2T const& point) {
T distSqu = (point - m_position).magnitudeSquared();
return distSqu <= m_radius * m_radius;
}
template<typename T>
auto Circle<T>::pointAtAngle(T angle) -> Vec2T {
auto fromCenter = Vec2T::withAngle(angle, m_radius);
return fromCenter + m_position;
}
template<typename T>
auto Circle<T>::angleNearestTo(Vec2T const& point) -> T {
return (point - m_position).angle();
}
template<typename T>
auto Circle<T>::pointNearestTo(Vec2T const& point) -> Vec2T {
T angle = angleNearestTo(point);
return pointAtAngle(angle);
}
typedef Circle<float> CirF;
typedef Circle<double> CirD;
}
#endif

View file

@ -0,0 +1,303 @@
#include "StarFlowingLiquidAgent.hpp"
#include "StarRoot.hpp"
#include "StarLiquidsDatabase.hpp"
#include "StarLivingWorldAgent.hpp"
#include "StarLogging.hpp"
namespace Star {
FlowingLiquidAgent::FlowingLiquidAgent() {
m_livingWorld = {};
}
void FlowingLiquidAgent::bind(LivingWorldFacadePtr world, LivingWorldAgent* livingWorld) {
this->m_world = world;
m_livingWorld = livingWorld;
}
void FlowingLiquidAgent::processLocation(Vec2I const& c) {
auto level = getLiquidLevel(c.x(), c.y());
int fountainPressure = 0;
if (level.liquid != Liquid::SolidTileLiquidPseudoId) {
if (!hasBackground(c.x(), c.y()) && isOcean(c.x(), c.y())) {
auto liquid = oceanLiquid(c.x(), c.y());
auto pressure = oceanLiquidPressure(c.x(), c.y());
if ((level.liquid != liquid)||(level.level != pressure)) {
setLiquidLevel(c.x(), c.y(), LiquidLevel{liquid, pressure});
level = getLiquidLevel(c.x(), c.y());
}
}
}
if (level.level >= Liquid::OverflowFullLiquidLevel)
if (moveLiquidUpwards(c, level)) {
auto l = getLiquidLevel(c.x(), c.y());
fountainPressure = (int) level.level - (int) l.level;
level = l;
}
if (level.level > 0)
if (moveLiquidDown(c, level, true)) {
level = getLiquidLevel(c.x(), c.y());
fountainPressure = 0;
}
if (level.level >= Liquid::TrivialLevelThreshold) {
moveLiquidSideWays(c, fountainPressure);
}
}
bool FlowingLiquidAgent::moveLiquidDown(Vec2I c, LiquidLevel above, bool attemptMoveOut) {
auto liquidsDatabase = Root::singleton().liquidsDatabase();
if (above.liquid == Liquid::SolidTileLiquidPseudoId)
return false;
auto below = getLiquidLevel(c.x(), c.y() - 1);
if (below.liquid == Liquid::SolidTileLiquidPseudoId) {
if (attemptMoveOut)
moveLiquidOut(c);
return false;
}
if ((c.y() <= 0) && liquidsDatabase->liquidSettings(above.liquid)->endless)
return false;
int move = std::max(0,
std::min(std::min((int) above.level, (Liquid::MaxLiquidLevel - 1) - (int) below.level),
std::max((Liquid::OverflowFullLiquidLevel - 1) - (int) below.level,
std::min((Liquid::PressurePerLevel - ((int) below.level - (int) above.level)) / 2,
(int) above.level - (Liquid::TrivialLevelThreshold - 1)))));
move *= std::min(liquidsDatabase->liquidSettings(below.liquid)->downwardsSpeedModifier, liquidsDatabase->liquidSettings(above.liquid)->downwardsSpeedModifier);
above.level -= move;
below.level += move;
below.liquid = above.liquid;
if (move > 0) {
moveLiquid(c.x(), c.y(), 0, -1, above, below);
return true;
} else {
if (attemptMoveOut)
moveLiquidOut(c);
return false;
}
}
bool FlowingLiquidAgent::moveLiquidSideWays(Vec2I c, int fountain) {
fountain /= Liquid::PressurePerLevel;
auto below = getLiquidLevel(c.x(), c.y() - 1);
if ((below.liquid != Liquid::SolidTileLiquidPseudoId) && (below.level < Liquid::FullLiquidLevel))
return false;
bool result = false;
int move = 0;
while (true) {
move++;
int bdx = 0;
int bl = 0;
int l;
auto level = getLiquidLevel(c.x(), c.y());
if (level.liquid == Liquid::SolidTileLiquidPseudoId)
break;
int idx = 0;
int preferredDirection = m_random.randInt(1);
for (int dx = -1; dx <= 1; dx += 2) {
l = checkMoveLiquidSideways(c.x() + dx, c.y(), level);
if ((l > bl) || ((l == bl) && (idx == preferredDirection))) {
bl = l;
bdx = dx;
}
idx++;
}
if (bl == 0)
break;
auto source = getLiquidLevel(c.x(), c.y());
auto target = getLiquidLevel(c.x() + bdx, c.y());
if ((source.level > bl) && ((int) source.level - bl > (int) target.level + bl))
bl++;
if ((fountain > 0) && (m_random.randInt(5 * fountain) != 0))
bl /= fountain * 10;
if ((bl >= Liquid::TrivialSidewaysMoveThreshold)
&& (((int) source.level - bl) >= Liquid::TrivialLevelThreshold)
&& (((int) target.level + bl) >= Liquid::TrivialLevelThreshold)) {
source.level -= bl;
target.level += bl;
target.liquid = source.liquid;
moveLiquid(c.x(), c.y(), bdx, 0, source, target);
moveLiquidDown(Vec2I(c.x() + bdx, c.y()), getLiquidLevel(c.x() + bdx, c.y()), false);
result = true;
}
else {
if (target.level <= Liquid::IdleLevelEvaporateThreshold) {
source.level = std::max(0, source.level - Liquid::IdleLevelEvaporateThreshold);
setLiquidLevel(c.x(), c.y(), source);
result = true;
}
}
break;
}
return result;
}
int FlowingLiquidAgent::checkMoveLiquidSideways(int x, int y, LiquidLevel level) {
if (level.liquid == Liquid::SolidTileLiquidPseudoId)
return 0;
auto beside = getLiquidLevel(x, y);
if (beside.liquid == Liquid::SolidTileLiquidPseudoId)
return 0;
int result = (((int) level.level - (int) beside.level) * 1.9) / 2;
auto liquidsDatabase = Root::singleton().liquidsDatabase();
result *= std::min(liquidsDatabase->liquidSettings(level.liquid)->sidewaysSpeedModifier, liquidsDatabase->liquidSettings(beside.liquid)->sidewaysSpeedModifier);
if (result > level.level)
result = level.level;
if (result <= 0)
return 0;
return result;
}
bool FlowingLiquidAgent::moveLiquidUpwards(Vec2I c, LiquidLevel level) {
if (level.liquid == Liquid::SolidTileLiquidPseudoId)
return false;
if (level.level < Liquid::OverflowFullLiquidLevel) {
return false;
}
auto above = getLiquidLevel(c.x(), c.y() + 1);
if (above.liquid == Liquid::SolidTileLiquidPseudoId) {
return false;
}
int move = std::max(0,
std::min((int) level.level - (Liquid::OverflowFullLiquidLevel -1),
(((int) level.level - (int) above.level) - (Liquid::PressurePerLevel)) / 2));
if (above.liquid == NullLiquidId)
above.liquid = level.liquid;
auto liquidsDatabase = Root::singleton().liquidsDatabase();
move *= std::min(liquidsDatabase->liquidSettings(level.liquid)->upwardsSpeedModifier, liquidsDatabase->liquidSettings(above.liquid)->upwardsSpeedModifier);
move = std::min(move, std::min((int) level.level, (Liquid::MaxLiquidLevel - 1) - (int) above.level));
above.level += move;
level.level -= move;
above.liquid = level.liquid;
if ((move >= Liquid::TrivialUpwardsMoveThreshold)
&& (above.level >= Liquid::TrivialLevelThreshold)) {
moveLiquid(c.x(), c.y(), 0, 1, level, above);
return true;
}
return false;
}
bool FlowingLiquidAgent::moveLiquidOut(Vec2I c) {
if (!hasBackground(c.x(), c.y()) && !isOcean(c.x(), c.y())) {
auto current = getLiquidLevel(c.x(), c.y());
auto liquidsDatabase = Root::singleton().liquidsDatabase();
auto setting = liquidsDatabase->liquidSettings(current.liquid);
if (setting->endless)
return false;
// Move Liquid into the background (disappear) at the BackgroundFlowPercentage
current.level *= 1.0f - setting->backgroundFlowPercentage;
setLiquidLevel(c.x(), c.y(), current);
return true;
} else {
return false;
}
}
bool FlowingLiquidAgent::sanityCheckLiquid(LiquidLevel const& level) const {
if (((level.liquid != Liquid::SolidTileLiquidPseudoId)&&(level.liquid != NullLiquidId)) == (level.level == 0)) {
Logger::warn("Inconsistent liquid data. Liquid:%s Level:%s", level.liquid, level.level);
return false;
}
return true;
}
LiquidLevel FlowingLiquidAgent::getLiquidLevel(int x, int y) {
LiquidLevel level = m_world->readTileLiquid(x, y);
#ifndef NDEBUG
sanityCheckLiquid(level);
#endif
return level;
}
bool FlowingLiquidAgent::hasBackground(int x, int y) {
return m_world->hasBackground(x, y);
}
bool FlowingLiquidAgent::isOcean(int x, int y) {
return m_world->isOcean(x, y);
}
LiquidId FlowingLiquidAgent::oceanLiquid(int x, int y) {
return m_world->oceanLiquid(x, y);
}
uint16_t FlowingLiquidAgent::oceanLiquidPressure(int x, int y) {
return m_world->oceanLiquidPressure(x, y);
}
void FlowingLiquidAgent::moveLiquid(int x, int y, int dx, int dy,
LiquidLevel proposedSourceLevel, LiquidLevel proposedTargetLevel) {
setLiquidLevel(x, y, proposedSourceLevel);
setLiquidLevel(x + dx, y + dy, proposedTargetLevel);
}
void FlowingLiquidAgent::setLiquidLevel(int x, int y, LiquidLevel level) {
if (level.level <= Liquid::TrivialLevelThreshold)
level.level = 0;
if (level.level == 0)
level.liquid = NullLiquidId;
auto prevLevel = getLiquidLevel(x, y);
auto liquidsDatabase = Root::singleton().liquidsDatabase();
auto prevLevelSetting = liquidsDatabase->liquidSettings(prevLevel.liquid);
if (prevLevelSetting->endless)
return; // can never update a endlesswater tile after generation
if ((prevLevel.liquid == level.liquid) && (prevLevel.level == level.level))
return;
starAssert(prevLevel.liquid != Liquid::SolidTileLiquidPseudoId);
MaterialId blockId = NullMaterialId;
MaterialHue blockHueShift = MaterialHue();
float flow = fabs((float) level.level - (float) prevLevel.level);
auto effect = liquidsDatabase->liquidSettings(level.liquid);
level.liquid = effect->flowsAs;
float chance = effect->blockGenerationChance * fmin((float)flow / (float)Liquid::FullLiquidLevel, 1.0f);
if (Random::randf() < chance) {
if (!effect->connectedOnly
||(getLiquidLevel(x-1, y).liquid == Liquid::SolidTileLiquidPseudoId)
||(getLiquidLevel(x+1, y).liquid == Liquid::SolidTileLiquidPseudoId)
||(getLiquidLevel(x, y-1).liquid == Liquid::SolidTileLiquidPseudoId)
||(getLiquidLevel(x, y+1).liquid == Liquid::SolidTileLiquidPseudoId)) {
blockId = Random::randFrom(effect->blockOptions);
}
}
if ((level.liquid != NullLiquidId) && (level.liquid != Liquid::SolidTileLiquidPseudoId) && (prevLevel.liquid != NullLiquidId) && (prevLevel.liquid != Liquid::SolidTileLiquidPseudoId)
&& (level.liquid != prevLevel.liquid)) {
auto& interaction = effect->liquidInteraction[prevLevel.liquid];
if (Random::randf() < interaction.blockGenerationChance) {
blockId = Random::randFrom(interaction.blockOptions);
} else {
float chance = flow / (flow + prevLevel.level);
if (Random::randf() >= chance)
level.liquid = prevLevel.liquid;
}
}
if ((blockId != NullMaterialId) && (blockId != EmptyMaterialId)) {
m_world->writeTileMaterial(x, y, blockId, blockHueShift, true);
} else {
if (!sanityCheckLiquid(level))
return;
m_world->writeTileLiquid(x, y, level);
}
m_livingWorld->visitRegion(x, y, true);
}
}

View file

@ -0,0 +1,47 @@
#ifndef STAR_FLOWING_LIQUID_AGENT_HPP
#define STAR_FLOWING_LIQUID_AGENT_HPP
#include "StarRandom.hpp"
#include "StarVector.hpp"
#include "StarLiquidTypes.hpp"
namespace Star {
STAR_CLASS(LivingWorldAgent);
STAR_CLASS(LivingWorldFacade);
STAR_CLASS(FlowingLiquidAgent);
class FlowingLiquidAgent {
public:
FlowingLiquidAgent();
void bind(LivingWorldFacadePtr world, LivingWorldAgent* livingWorld);
void processLocation(Vec2I const& location);
private:
LiquidLevel getLiquidLevel(int x, int y);
bool hasBackground(int x, int y);
bool isOcean(int x, int y);
LiquidId oceanLiquid(int x, int y);
uint16_t oceanLiquidPressure(int x, int y);
void moveLiquid(int x, int y, int dx, int dy, LiquidLevel proposedSourceLevel, LiquidLevel proposedTargetLevel);
void setLiquidLevel(int x, int y, LiquidLevel level);
bool moveLiquidDown(Vec2I c, LiquidLevel above, bool attemptMoveOut);
bool moveLiquidSideWays(Vec2I c, int fountain);
int checkMoveLiquidSideways(int x, int y, LiquidLevel level);
bool moveLiquidUpwards(Vec2I c, LiquidLevel level);
bool moveLiquidOut(Vec2I c);
bool sanityCheckLiquid(LiquidLevel const& level) const;
LivingWorldAgent* m_livingWorld;
RandomSource m_random;
LivingWorldFacadePtr m_world;
};
}
#endif

View file

@ -0,0 +1,39 @@
#include "StarLiveCounter.hpp"
#include "StarMap.hpp"
#include "StarLogging.hpp"
#include "StarThread.hpp"
namespace Star {
struct LiveCounterData {
Mutex lock;
HashMap<std::type_index, unique_ptr<LiveAtomicCounter>> counters;
};
LiveCounterData& liveCounterData() {
static LiveCounterData data;
return data;
};
void bindLiveCounter(std::type_index const& typeIndex, LiveAtomicCounter*& counter) {
if (!counter) {
auto& data = liveCounterData();
MutexLocker locker(data.lock);
auto& ptr = data.counters[typeIndex];
if (!ptr)
ptr = make_unique<LiveAtomicCounter>();
counter = ptr.get();
}
}
void dumpLiveCounters() {
#ifdef STAR_ENABLE_LIVECOUNTER
auto& data = liveCounterData();
Logger::info("LiveCounters");
MutexLocker locker(data.lock);
for (auto const& e : data.counters)
Logger::info(" %s %s", e.first.name(), e.second->load());
#endif
}
}

View file

@ -0,0 +1,54 @@
#ifndef STAR_LIVE_COUNTER_HPP
#define STAR_LIVE_COUNTER_HPP
#include "StarConfig.hpp"
#include <typeindex>
namespace Star {
typedef atomic<int64_t> LiveAtomicCounter;
void bindLiveCounter(std::type_index const& typeIndex, LiveAtomicCounter*& counter);
void dumpLiveCounters();
// Use as class MyClass : LiveCounter<MyClass> { ... }
template<typename T>
class LiveCounter {
public:
#ifdef STAR_ENABLE_LIVECOUNTER
LiveCounter() {
bindLiveCounter(typeid(T), s_liveCounter);
++(*s_liveCounter);
}
LiveCounter(LiveCounter const&) {
bindLiveCounter(typeid(T), s_liveCounter);
++(*s_liveCounter);
}
LiveCounter(LiveCounter&&) {
bindLiveCounter(typeid(T), s_liveCounter);
++(*s_liveCounter);
}
void operator=(LiveCounter const&) {}
void operator=(LiveCounter&&) {}
~LiveCounter() {
--(*s_liveCounter);
}
private:
static LiveAtomicCounter* s_liveCounter;
#endif
};
#ifdef STAR_ENABLE_LIVECOUNTER
template<typename T>
LiveAtomicCounter* LiveCounter<T>::s_liveCounter = nullptr;
#endif
}
#endif

View file

@ -0,0 +1,705 @@
#include "StarNetStates.hpp"
namespace Star {
bool NetStatesField::pullUpdated() const {
return m_field->pullUpdated();
}
bool NetStatesField::connected() const {
return !m_field.unique();
}
NetStatesField::NetStatesField(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value)
: m_field(make_shared<NetStatesDetail::Field>(move(typeInfo), move(value))) {}
NetStatesInt NetStatesInt::makeInt8(int64_t initialValue) {
return NetStatesInt(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::Int8, {}, {}, {}}, initialValue);
}
NetStatesInt NetStatesInt::makeInt16(int64_t initialValue) {
return NetStatesInt(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::Int16, {}, {}, {}}, initialValue);
}
NetStatesInt NetStatesInt::makeVarInt(int64_t initialValue) {
return NetStatesInt(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::VarInt, {}, {}, {}}, initialValue);
}
NetStatesInt::NetStatesInt(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value)
: NetStatesField(move(typeInfo), move(value)) {}
int64_t NetStatesInt::get() const {
return m_field->getInt();
}
void NetStatesInt::set(int64_t value) {
m_field->setInt(value);
}
NetStatesUInt NetStatesUInt::makeUInt8(uint64_t initialValue) {
return NetStatesUInt(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::UInt8, {}, {}, {}}, initialValue);
}
NetStatesUInt NetStatesUInt::makeUInt16(uint64_t initialValue) {
return NetStatesUInt(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::UInt16, {}, {}, {}}, initialValue);
}
NetStatesUInt NetStatesUInt::makeVarUInt(uint64_t initialValue) {
return NetStatesUInt(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::VarUInt, {}, {}, {}}, initialValue);
}
NetStatesUInt::NetStatesUInt(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value)
: NetStatesField(move(typeInfo), move(value)) {}
uint64_t NetStatesUInt::get() const {
return m_field->getUInt();
}
void NetStatesUInt::set(uint64_t value) {
m_field->setUInt(value);
}
NetStatesSize::NetStatesSize(size_t initialValue)
: NetStatesField(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::Size, {}, {}, {}}, initialValue) {}
size_t NetStatesSize::get() const {
return m_field->getSize();
}
void NetStatesSize::set(size_t value) {
m_field->setSize(value);
}
NetStatesFloat NetStatesFloat::makeFloat(double initialValue, NetStatesInterpolator interpolator) {
return NetStatesFloat(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::Float, {}, {}, move(interpolator)}, initialValue);
}
NetStatesFloat NetStatesFloat::makeDouble(double initialValue, NetStatesInterpolator interpolator) {
return NetStatesFloat(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::Double, {}, {}, move(interpolator)}, initialValue);
}
NetStatesFloat NetStatesFloat::makeNormalizedFloat8(double initialValue, NetStatesInterpolator interpolator) {
return NetStatesFloat(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::NFloat8, {}, {}, move(interpolator)}, initialValue);
}
NetStatesFloat NetStatesFloat::makeNormalizedFloat16(double initialValue, NetStatesInterpolator interpolator) {
return NetStatesFloat(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::NFloat16, {}, {}, move(interpolator)}, initialValue);
}
NetStatesFloat NetStatesFloat::makeFixedPoint8(double base, double initialValue, NetStatesInterpolator interpolator) {
return NetStatesFloat(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::Fixed8, base, {}, move(interpolator)}, initialValue);
}
NetStatesFloat NetStatesFloat::makeFixedPoint16(double base, double initialValue, NetStatesInterpolator interpolator) {
return NetStatesFloat(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::Fixed16, base, {}, move(interpolator)}, initialValue);
}
NetStatesFloat NetStatesFloat::makeFixedPoint(double base, double initialValue, NetStatesInterpolator interpolator) {
return NetStatesFloat(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::VarFixed, base, {}, move(interpolator)}, initialValue);
}
double NetStatesFloat::get() const {
return m_field->getFloat();
}
void NetStatesFloat::set(double value) {
m_field->setFloat(value);
}
void NetStatesFloat::setInterpolator(NetStatesInterpolator interpolator) {
m_field->setFloatInterpolator(move(interpolator));
}
NetStatesFloat::NetStatesFloat(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value)
: NetStatesField(move(typeInfo), move(value)) {}
NetStatesBool::NetStatesBool(bool initialValue)
: NetStatesField(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::Bool, {}, {}, {}}, initialValue) {}
bool NetStatesBool::get() const {
return m_field->getBool();
}
void NetStatesBool::set(bool value) {
m_field->setBool(value);
}
NetStatesEvent::NetStatesEvent()
: NetStatesField(NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::Event, {}, {}, {}}, (uint64_t)0) {}
void NetStatesEvent::trigger() {
m_field->triggerEvent();
}
uint64_t NetStatesEvent::pullOccurrences() {
return m_field->pullOccurrences();
}
bool NetStatesEvent::pullOccurred() {
return pullOccurrences() != 0;
}
void NetStatesEvent::ignoreOccurrences() {
m_field->ignoreOccurrences();
}
NetStepStates::NetStepStates() {
clearFields();
}
void NetStepStates::addField(NetStatesField const& nsfield) {
if (nsfield.connected())
throw NetStatesException("Field added in NetStepStates is already connected");
auto field = nsfield.m_field;
field->updateStep(m_currentStep);
if (m_interpolationEnabled)
field->enableInterpolation(m_extrapolationSteps);
m_fields.append(field);
}
void NetStepStates::clearFields() {
for (auto const& field : m_fields) {
field->disableInterpolation();
field->resetStep();
}
m_fields.clear();
m_currentStep = 0;
m_interpolationEnabled = false;
m_extrapolationSteps = 0;
}
uint32_t NetStepStates::fieldDigest() const {
uint32_t digest;
fnv_hash32_init(digest);
for (auto const& field : m_fields) {
auto const& typeInfo = field->typeInfo();
uint8_t type = (uint8_t)typeInfo.type;
float base = typeInfo.fixedPointBase.value(0.0f);
toBigEndian(type);
toBigEndian(base);
fnv_hash32_bytes(digest, (char const*)&type, sizeof(type));
fnv_hash32_bytes(digest, (char const*)&base, sizeof(base));
}
return digest;
}
bool NetStepStates::updateStep(uint64_t step) {
if (step < m_currentStep) {
throw NetStatesException("step decreased in NetStepStates::updateStep");
} else if (step > m_currentStep) {
m_currentStep = step;
bool updated = false;
for (auto const& field : m_fields)
updated |= field->updateStep(m_currentStep);
return updated;
} else {
return false;
}
}
uint64_t NetStepStates::currentStep() const {
return m_currentStep;
}
void NetStepStates::resetStep() {
m_currentStep = 0;
for (auto& field : m_fields)
field->resetStep();
}
void NetStepStates::enableInterpolation(uint64_t extrapolationSteps) {
m_interpolationEnabled = true;
m_extrapolationSteps = extrapolationSteps;
for (auto& field : m_fields)
field->enableInterpolation(extrapolationSteps);
}
void NetStepStates::disableInterpolation() {
m_interpolationEnabled = false;
m_extrapolationSteps = 0.0;
for (auto& field : m_fields)
field->disableInterpolation();
}
bool NetStepStates::interpolationEnabled() const {
return m_interpolationEnabled;
}
bool NetStepStates::hasDelta(uint64_t fromStep) const {
for (auto const& field : m_fields) {
if (field->hasDelta(fromStep))
return true;
}
return false;
}
void NetStepStates::writeDelta(DataStream& ds, uint64_t fromStep) const {
for (size_t i = 0; i < m_fields.size(); ++i) {
auto const& field = m_fields[i];
if (field->hasDelta(fromStep)) {
ds.writeVlqU(i + 1);
field->writeField(ds);
}
}
ds.writeVlqU(0);
}
bool NetStepStates::readDelta(DataStream& ds, double predictedDeltaStep) {
bool updated = false;
while (true) {
uint64_t index = ds.readVlqU();
if (index == 0)
break;
--index;
updated |= m_fields.at(index)->readField(ds, predictedDeltaStep);
}
interpolationHeartbeat(predictedDeltaStep);
return updated;
}
void NetStepStates::writeFull(DataStream& ds) const {
ds.write(fieldDigest());
for (auto const& field : m_fields)
field->writeField(ds);
}
void NetStepStates::readFull(DataStream& ds, double predictedDeltaStep) {
auto digest = ds.read<uint32_t>();
if (digest != fieldDigest())
throw NetStatesException("Type mismatch in NetStepStates::readFull");
for (auto const& field : m_fields)
field->readField(ds, predictedDeltaStep);
}
ByteArray NetStepStates::writeDeltaPacket(uint64_t fromStep) const {
DataStreamBuffer ds;
for (size_t i = 0; i < m_fields.size(); ++i) {
auto const& field = m_fields[i];
if (field->hasDelta(fromStep)) {
ds.writeVlqU(i);
field->writeField(ds);
}
}
return ds.takeData();
}
bool NetStepStates::readDeltaPacket(ByteArray packet, double predictedDeltaStep) {
DataStreamBuffer ds(move(packet));
bool updated = false;
while (!ds.atEnd()) {
uint64_t index = ds.readVlqU();
updated |= m_fields.at(index)->readField(ds, predictedDeltaStep);
}
interpolationHeartbeat(predictedDeltaStep);
return updated;
}
ByteArray NetStepStates::writeFullPacket() const {
DataStreamBuffer ds;
writeFull(ds);
return ds.takeData();
}
void NetStepStates::readFullPacket(ByteArray packet, double predictedDeltaStep) {
DataStreamBuffer ds(move(packet));
readFull(ds, predictedDeltaStep);
}
void NetStepStates::interpolationHeartbeat(double predictedDeltaStep) {
if (m_interpolationEnabled) {
for (auto const& field : m_fields)
field->interpolationHeartbeat(predictedDeltaStep);
}
}
NetSyncStates::NetSyncStates() {}
bool NetSyncStates::hasDelta() const {
return NetStepStates::hasDelta(currentStep());
}
void NetSyncStates::writeDelta(DataStream& ds) {
NetStepStates::writeDelta(ds, currentStep());
updateStep(currentStep() + 1);
}
bool NetSyncStates::readDelta(DataStream& ds) {
updateStep(currentStep() + 1);
return NetStepStates::readDelta(ds, currentStep());
}
ByteArray NetSyncStates::writeDeltaPacket() {
auto packet = NetStepStates::writeDeltaPacket(currentStep());
updateStep(currentStep() + 1);
return packet;
}
bool NetSyncStates::readDeltaPacket(ByteArray packet) {
updateStep(currentStep() + 1);
return NetStepStates::readDeltaPacket(move(packet), currentStep());
}
void NetSyncStates::reset() {
NetStepStates::resetStep();
}
NetStatesDetail::Value NetStatesDetail::TypeInfo::valueFromTransmission(NetStatesDetail::TransmissionValue const& transmission) const {
if (type == TransmissionType::Int8) {
return (int64_t)transmission.get<int8_t>();
} else if (type == TransmissionType::UInt8) {
return (uint64_t)transmission.get<uint8_t>();
} else if (type == TransmissionType::Int16) {
return (int64_t)transmission.get<int16_t>();
} else if (type == TransmissionType::UInt16) {
return (uint64_t)transmission.get<uint16_t>();
} else if (type == TransmissionType::VarInt) {
return (int64_t)transmission.get<int64_t>();
} else if (type == TransmissionType::VarUInt) {
return (uint64_t)transmission.get<uint64_t>();
} else if (type == TransmissionType::Size) {
uint64_t u = transmission.get<uint64_t>();
if (u == 0)
return NPos;
return (size_t)(u - 1);
} else if (type == TransmissionType::Float) {
return (double)transmission.get<float>();
} else if (type == TransmissionType::Double) {
return transmission.get<double>();
} else if (type == TransmissionType::NFloat8) {
return transmission.get<uint8_t>() / 255.0;
} else if (type == TransmissionType::NFloat16) {
return transmission.get<uint16_t>() / 65535.0;
} if (type == TransmissionType::Fixed8) {
return transmission.get<int8_t>() * *fixedPointBase;
} else if (type == TransmissionType::Fixed16) {
return transmission.get<int16_t>() * *fixedPointBase;
} else if (type == TransmissionType::VarFixed) {
return transmission.get<int64_t>() * *fixedPointBase;
} else if (type == TransmissionType::Bool) {
return transmission.get<bool>();
} else if (type == TransmissionType::Event) {
return transmission.get<uint64_t>();
} else {
return transmission.get<shared_ptr<void>>();
}
}
NetStatesDetail::TransmissionValue NetStatesDetail::TypeInfo::transmissionFromValue(NetStatesDetail::Value const& value) const {
if (type == TransmissionType::Int8) {
return (int8_t)value.get<int64_t>();
} else if (type == TransmissionType::UInt8) {
return (uint8_t)value.get<uint64_t>();
} else if (type == TransmissionType::Int16) {
return (int16_t)value.get<int64_t>();
} else if (type == TransmissionType::UInt16) {
return (uint16_t)value.get<uint64_t>();
} else if (type == TransmissionType::VarInt) {
return (int64_t)value.get<int64_t>();
} else if (type == TransmissionType::VarUInt) {
return (uint64_t)value.get<uint64_t>();
} else if (type == TransmissionType::Size) {
size_t s = value.get<size_t>();
if (s == NPos)
return (uint64_t)0;
return (uint64_t)(s + 1);
} else if (type == TransmissionType::Float) {
return (float)value.get<double>();
} else if (type == TransmissionType::Double) {
return (double)value.get<double>();
} else if (type == TransmissionType::NFloat8) {
return (uint8_t)round(clamp(value.get<double>(), 0.0, 1.0) * 255.0);
} else if (type == TransmissionType::NFloat16) {
return (uint16_t)round(clamp(value.get<double>(), 0.0, 1.0) * 65535.0);
} else if (type == TransmissionType::Fixed8) {
return (int8_t)clamp<int64_t>(round(value.get<double>() / *fixedPointBase), -128, 127);
} else if (type == TransmissionType::Fixed16) {
return (int16_t)clamp<int64_t>(round(value.get<double>() / *fixedPointBase), -32768, 32767);
} else if (type == TransmissionType::VarFixed) {
return (int64_t)round(value.get<double>() / *fixedPointBase);
} else if (type == TransmissionType::Bool) {
return value.get<bool>();
} else if (type == TransmissionType::Event) {
return value.get<uint64_t>();
} else {
return value.get<shared_ptr<void>>();
}
}
NetStatesDetail::Field::Field(TypeInfo typeInfo, Value value)
: m_typeInfo(move(typeInfo)), m_currentStep(0), m_value(move(value)),
m_unpulledUpdate(true), m_pulledOccurrences(0), m_transmissionDirty(true),
m_latestTransmissionStep(0) {
freshenTransmission();
}
NetStatesDetail::TypeInfo const& NetStatesDetail::Field::typeInfo() const {
return m_typeInfo;
}
int64_t NetStatesDetail::Field::getInt() const {
return m_value.get<int64_t>();
}
void NetStatesDetail::Field::setInt(int64_t value) {
if (m_typeInfo.type == TransmissionType::Int8)
value = clamp<int64_t>(value, -128, 127);
else if (m_typeInfo.type == TransmissionType::Int16)
value = clamp<int64_t>(value, -32768, 32767);
if (m_value != value) {
m_value = value;
markValueUpdated();
}
}
uint64_t NetStatesDetail::Field::getUInt() const {
return m_value.get<uint64_t>();
}
void NetStatesDetail::Field::setUInt(uint64_t value) {
if (m_typeInfo.type == TransmissionType::UInt8)
value = clamp<uint64_t>(value, 0, 255);
else if (m_typeInfo.type == TransmissionType::UInt16)
value = clamp<uint64_t>(value, 0, 65535);
if (m_value != value) {
m_value = value;
markValueUpdated();
}
}
size_t NetStatesDetail::Field::getSize() const {
return m_value.get<size_t>();
}
void NetStatesDetail::Field::setSize(size_t value) {
if (m_value != value) {
m_value = value;
markValueUpdated();
}
}
double NetStatesDetail::Field::getFloat() const {
return m_value.get<double>();
}
void NetStatesDetail::Field::setFloat(double value) {
if (m_typeInfo.type == TransmissionType::NFloat8 || m_typeInfo.type == TransmissionType::NFloat16)
value = clamp<double>(value, 0.0, 1.0);
else if (m_typeInfo.type == TransmissionType::Fixed8)
value = clamp<double>(value, -128.0 * *m_typeInfo.fixedPointBase, 127.0 * *m_typeInfo.fixedPointBase);
else if (m_typeInfo.type == TransmissionType::Fixed16)
value = clamp<double>(value, -32768.0 * *m_typeInfo.fixedPointBase, 32767.0 * *m_typeInfo.fixedPointBase);
if (m_value != value) {
m_value = value;
markValueUpdated();
}
}
void NetStatesDetail::Field::setFloatInterpolator(NetStatesInterpolator interpolator) {
m_typeInfo.interpolator = move(interpolator);
}
bool NetStatesDetail::Field::getBool() const {
return m_value.get<bool>();
}
void NetStatesDetail::Field::setBool(bool value) {
if (m_value != value) {
m_value = value;
markValueUpdated();
}
}
void NetStatesDetail::Field::triggerEvent() {
++m_value.get<uint64_t>();
markValueUpdated();
}
uint64_t NetStatesDetail::Field::pullOccurrences() {
uint64_t occurrences = m_value.get<uint64_t>();
starAssert(occurrences >= m_pulledOccurrences);
uint64_t unchecked = occurrences - m_pulledOccurrences;
m_pulledOccurrences = occurrences;
return unchecked;
}
void NetStatesDetail::Field::ignoreOccurrences() {
m_pulledOccurrences = m_value.get<uint64_t>();
}
bool NetStatesDetail::Field::pullUpdated() {
return take(m_unpulledUpdate);
}
bool NetStatesDetail::Field::updateStep(uint64_t step) {
if (m_interpolatedTransmissions) {
freshenTransmission();
if (m_interpolatedTransmissions->updateStep(step))
return updateValueFromTransmission();
}
m_currentStep = step;
return false;
}
void NetStatesDetail::Field::resetStep() {
freshenTransmission();
m_currentStep = 0;
m_latestTransmissionStep = 0;
if (m_interpolatedTransmissions) {
m_interpolatedTransmissions->reset();
m_interpolatedTransmissions->appendDataPoint(m_currentStep, m_latestTransmission);
}
}
bool NetStatesDetail::Field::hasDelta(uint64_t fromStep) const {
const_cast<Field*>(this)->freshenTransmission();
return m_latestTransmissionStep >= fromStep;
}
bool NetStatesDetail::Field::readField(DataStream& ds, double interpolationStep) {
freshenTransmission();
if (m_typeInfo.type == TransmissionType::Int8 || m_typeInfo.type == TransmissionType::Fixed8) {
ds.read(m_latestTransmission.get<int8_t>());
} else if (m_typeInfo.type == TransmissionType::UInt8 || m_typeInfo.type == TransmissionType::NFloat8) {
ds.read(m_latestTransmission.get<uint8_t>());
} else if (m_typeInfo.type == TransmissionType::Int16 || m_typeInfo.type == TransmissionType::Fixed16) {
ds.read(m_latestTransmission.get<int16_t>());
} else if (m_typeInfo.type == TransmissionType::UInt16 || m_typeInfo.type == TransmissionType::NFloat16) {
ds.read(m_latestTransmission.get<uint16_t>());
} else if (m_typeInfo.type == TransmissionType::VarInt || m_typeInfo.type == TransmissionType::VarFixed) {
m_latestTransmission.get<int64_t>() = ds.readVlqI();
} else if (m_typeInfo.type == TransmissionType::VarUInt || m_typeInfo.type == TransmissionType::Size || m_typeInfo.type == TransmissionType::Event) {
m_latestTransmission.get<uint64_t>() = ds.readVlqU();
} else if (m_typeInfo.type == TransmissionType::Float) {
ds.read(m_latestTransmission.get<float>());
} else if (m_typeInfo.type == TransmissionType::Double) {
ds.read(m_latestTransmission.get<double>());
} else if (m_typeInfo.type == TransmissionType::Bool) {
ds.read(m_latestTransmission.get<bool>());
} else if (m_typeInfo.type == TransmissionType::Generic) {
m_latestTransmission = shared_ptr<void>(m_typeInfo.genericSerializer->read(ds));
}
m_latestTransmissionStep = m_currentStep;
bool updated = true;
if (m_interpolatedTransmissions)
updated = m_interpolatedTransmissions->appendDataPoint(interpolationStep, m_latestTransmission);
if (updated)
updateValueFromTransmission();
return updated;
}
void NetStatesDetail::Field::writeField(DataStream& ds) const {
const_cast<Field*>(this)->freshenTransmission();
if (m_typeInfo.type == TransmissionType::Int8 || m_typeInfo.type == TransmissionType::Fixed8) {
ds.write(m_latestTransmission.get<int8_t>());
} else if (m_typeInfo.type == TransmissionType::UInt8 || m_typeInfo.type == TransmissionType::NFloat8) {
ds.write(m_latestTransmission.get<uint8_t>());
} else if (m_typeInfo.type == TransmissionType::Int16 || m_typeInfo.type == TransmissionType::Fixed16) {
ds.write(m_latestTransmission.get<int16_t>());
} else if (m_typeInfo.type == TransmissionType::UInt16 || m_typeInfo.type == TransmissionType::NFloat16) {
ds.write(m_latestTransmission.get<uint16_t>());
} else if (m_typeInfo.type == TransmissionType::VarInt || m_typeInfo.type == TransmissionType::VarFixed) {
ds.writeVlqI(m_latestTransmission.get<int64_t>());
} else if (m_typeInfo.type == TransmissionType::VarUInt || m_typeInfo.type == TransmissionType::Size || m_typeInfo.type == TransmissionType::Event) {
ds.writeVlqU(m_latestTransmission.get<uint64_t>());
} else if (m_typeInfo.type == TransmissionType::Float) {
ds.write(m_latestTransmission.get<float>());
} else if (m_typeInfo.type == TransmissionType::Double) {
ds.write(m_latestTransmission.get<double>());
} else if (m_typeInfo.type == TransmissionType::Bool) {
ds.write(m_latestTransmission.get<bool>());
} else if (m_typeInfo.type == TransmissionType::Generic) {
m_typeInfo.genericSerializer->write(ds, m_latestTransmission.get<shared_ptr<void>>());
}
}
void NetStatesDetail::Field::enableInterpolation(uint64_t extrapolationSteps) {
freshenTransmission();
m_interpolatedTransmissions.emplace();
m_interpolatedTransmissions->setExtrapolation(extrapolationSteps);
m_interpolatedTransmissions->appendDataPoint(m_currentStep, m_latestTransmission);
m_interpolatedTransmissions->updateStep(m_latestTransmissionStep);
m_interpolatedTransmissions->heartbeat(m_currentStep);
}
void NetStatesDetail::Field::disableInterpolation() {
freshenTransmission();
m_interpolatedTransmissions.reset();
}
void NetStatesDetail::Field::interpolationHeartbeat(double predictedDeltaStep) {
freshenTransmission();
if (m_interpolatedTransmissions)
m_interpolatedTransmissions->heartbeat(predictedDeltaStep);
}
bool NetStatesDetail::Field::updateValueFromTransmission() {
freshenTransmission();
Value newValue;
TransmissionValue storeExactValue;
if (m_interpolatedTransmissions) {
if (m_typeInfo.interpolator &&
(m_typeInfo.type == TransmissionType::Float || m_typeInfo.type == TransmissionType::Double ||
m_typeInfo.type == TransmissionType::NFloat8 || m_typeInfo.type == TransmissionType::NFloat16 ||
m_typeInfo.type == TransmissionType::Fixed8 || m_typeInfo.type == TransmissionType::Fixed16 ||
m_typeInfo.type == TransmissionType::VarFixed)) {
auto weighting = m_interpolatedTransmissions->weighting();
double min = m_typeInfo.valueFromTransmission(weighting.pointMin).get<double>();
double max = m_typeInfo.valueFromTransmission(weighting.pointMax).get<double>();
newValue = m_typeInfo.interpolator(weighting.offset, min, max);
} else {
newValue = m_typeInfo.valueFromTransmission(m_interpolatedTransmissions->exact());
}
} else {
newValue = m_typeInfo.valueFromTransmission(m_latestTransmission);
}
if (m_value != newValue) {
m_value = move(newValue);
m_unpulledUpdate = true;
return true;
} else {
return false;
}
}
void NetStatesDetail::Field::markValueUpdated() {
m_unpulledUpdate = true;
m_transmissionDirty = true;
}
void NetStatesDetail::Field::freshenTransmission() {
if (m_transmissionDirty) {
auto newTransmission = m_typeInfo.transmissionFromValue(m_value);
if (newTransmission.is<shared_ptr<void>>() || newTransmission != m_latestTransmission) {
m_latestTransmissionStep = m_currentStep;
m_latestTransmission = move(newTransmission);
}
if (m_interpolatedTransmissions) {
m_interpolatedTransmissions->reset();
m_interpolatedTransmissions->appendDataPoint(m_currentStep, m_latestTransmission);
}
m_transmissionDirty = false;
}
}
}

View file

@ -0,0 +1,611 @@
#ifndef STAR_NET_STATES_HPP
#define STAR_NET_STATES_HPP
#include "StarAlgorithm.hpp"
#include "StarAny.hpp"
#include "StarDynamicArray.hpp"
#include "StarDataStream.hpp"
#include "StarStepInterpolation.hpp"
namespace Star {
STAR_EXCEPTION(NetStatesException, StarException);
STAR_CLASS(NetStepStates);
// NetStates values that can be interpolated can be interpolated using the following methods:
//
// - "exact" interpolation, meaning that the interpolator simply does not look
// into the future at all and does no interpolation, which is the default.
// This is the same method that is used for values that cannot be
// interpolated at all.
//
// - "lerp" interpolation, or simple linear interpolation.
//
// - A custom interpolation function, for values that where it may not make
// sense to use linear interpolation, such as circular values.
typedef function<double(double, double, double)> NetStatesInterpolator;
NetStatesInterpolator const NetStatesExactInterpolator = NetStatesInterpolator();
NetStatesInterpolator const NetStatesLerpInterpolator = lerp<double, double>;
NetStatesInterpolator const NetStatesAngleInterpolator = angleLerp<double, double>;
namespace NetStatesDetail {
typedef Any<int64_t, uint64_t, size_t, double, bool, shared_ptr<void>> Value;
STAR_STRUCT(TypeInfo);
STAR_CLASS(Field);
struct GenericSerializer {
virtual ~GenericSerializer() = default;
virtual shared_ptr<void> read(DataStream& ds) const = 0;
virtual void write(DataStream& ds, shared_ptr<void> const& data) const = 0;
};
}
// A handle to a single field in a NetStates object. Fields are designed to be
// very fast to set and get, so they can generally be used in place of a
// regular variable without much cost.
class NetStatesField {
public:
NetStatesField(NetStatesField const&) = delete;
NetStatesField(NetStatesField&&) = default;
NetStatesField& operator=(NetStatesField const&) = delete;
NetStatesField& operator=(NetStatesField&&) = default;
// Pull whether or not the field has been updated since the last call to
// pullUpdated.
bool pullUpdated() const;
// Is this field connected to a NetStepStates object?
bool connected() const;
protected:
friend NetStepStates;
NetStatesField(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value);
NetStatesDetail::FieldPtr m_field;
};
// NetStates integers are clamped BOTH when setting and when receiving delta
// updates to the available bit width, 8, 16, or 64 bit.
class NetStatesInt : public NetStatesField {
public:
static NetStatesInt makeInt8(int64_t initialValue = 0);
static NetStatesInt makeInt16(int64_t initialValue = 0);
static NetStatesInt makeVarInt(int64_t initialValue = 0);
NetStatesInt(NetStatesInt&&) = default;
NetStatesInt& operator=(NetStatesInt&&) = default;
int64_t get() const;
void set(int64_t value);
private:
NetStatesInt(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value);
};
class NetStatesUInt : public NetStatesField {
public:
static NetStatesUInt makeUInt8(uint64_t initialValue = 0);
static NetStatesUInt makeUInt16(uint64_t initialValue = 0);
static NetStatesUInt makeVarUInt(uint64_t initialValue = 0);
NetStatesUInt(NetStatesUInt&&) = default;
NetStatesUInt& operator=(NetStatesUInt&&) = default;
uint64_t get() const;
void set(uint64_t value);
private:
NetStatesUInt(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value);
};
// Properly encodes NPos no matter the platform width of size_t NetStates
// size_t values are NOT clamped when setting.
class NetStatesSize : public NetStatesField {
public:
NetStatesSize(size_t initialValue = 0);
NetStatesSize(NetStatesSize&&) = default;
NetStatesSize& operator=(NetStatesSize&&) = default;
size_t get() const;
void set(size_t value);
};
// NetStates floats are clamped to their maximum and minimum legal values when
// calling set if the type is one of the normalized float types NFloat8 or
// NFloat16, or one of the fixed point types Fixed8 or Fixed16 (but not
// VarFixed)
class NetStatesFloat : public NetStatesField {
public:
static NetStatesFloat makeFloat(double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator);
static NetStatesFloat makeDouble(double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator);
// Float only from 0.0 to 1.0
static NetStatesFloat makeNormalizedFloat8(double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator);
static NetStatesFloat makeNormalizedFloat16(double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator);
// Float represented as a rational number of a fixed base
static NetStatesFloat makeFixedPoint8(double base, double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator);
static NetStatesFloat makeFixedPoint16(double base, double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator);
static NetStatesFloat makeFixedPoint(double base, double initialValue = 0.0, NetStatesInterpolator interpolator = NetStatesExactInterpolator);
NetStatesFloat(NetStatesFloat&&) = default;
NetStatesFloat& operator=(NetStatesFloat&&) = default;
double get() const;
void set(double value);
void setInterpolator(NetStatesInterpolator interpolator);
private:
NetStatesFloat(NetStatesDetail::TypeInfo typeInfo, NetStatesDetail::Value value);
};
class NetStatesBool : public NetStatesField {
public:
NetStatesBool(bool initialValue = false);
NetStatesBool(NetStatesBool&&) = default;
NetStatesBool& operator=(NetStatesBool&&) = default;
bool get() const;
void set(bool value);
};
// Wraps a uint64_t to give a simple event stream. Every trigger is an
// increment to a held uint64_t value, and slaves can see how many triggers
// have occurred since the last check.
class NetStatesEvent : public NetStatesField {
public:
NetStatesEvent();
NetStatesEvent(NetStatesEvent&&) = default;
NetStatesEvent& operator=(NetStatesEvent&&) = default;
void trigger();
// Returns the number of times this event has been triggered since the last
// pullOccurrences call.
uint64_t pullOccurrences();
// Pulls whether this event occurred at all, ignoring the number
bool pullOccurred();
// Ignore all the existing ocurrences
void ignoreOccurrences();
private:
using NetStatesField::pullUpdated;
};
// Holds an arbitrary serializable value
template<typename T>
class NetStatesData : public NetStatesField {
public:
NetStatesData(T initialValue = T());
NetStatesData(function<void(DataStream&, T&)> reader, function<void(DataStream&, T const&)> writer, T initialValue = T());
NetStatesData(NetStatesData&&) = default;
NetStatesData& operator=(NetStatesData&&) = default;
T const& get() const;
// Updates the value if the value is different than the existing value,
// requires T have operator==
void set(T const& value);
// Always updates the value and marks it as updated.
void push(T value);
// Update the value in place. The mutator will be called as bool
// mutator(T&), return true to signal that the value was updated.
template<typename Mutator>
void update(Mutator&& mutator);
private:
struct SerializerImpl : NetStatesDetail::GenericSerializer {
SerializerImpl(function<void(DataStream&, T&)> reader, function<void(DataStream&, T const&)> writer);
function<void(DataStream&, T&)> reader;
function<void(DataStream&, T const&)> writer;
shared_ptr<void> read(DataStream& ds) const override;
void write(DataStream& ds, shared_ptr<void> const& data) const override;
};
};
// Convenience around NetStatesInt to hold an enum value (avoids casting)
template<typename T>
class NetStatesEnum : public NetStatesField {
public:
NetStatesEnum(T initialValue = T());
T get() const;
void set(T value);
};
// Convenience for very common NetStatesData types
typedef NetStatesData<String> NetStatesString;
typedef NetStatesData<ByteArray> NetStatesBytes;
// Send and receives state using delta coding based on a always increasing step
// value. The step is never sent over the wire, rather it is used as a way to
// track versions of states.
class NetStepStates {
public:
NetStepStates();
template<typename... Args>
NetStepStates(Args const&... fields);
NetStepStates(NetStepStates const& states) = delete;
NetStepStates& operator=(NetStepStates const& states) = delete;
// Add the given unconnected field and connect it to this NetStepStates
// instance. Individual fields in a NetStates object are identified based on
// the order in which they are added. Both sides of a NetStates object must
// add the same number of fields and of the same type, and in the same order,
// for communication to work.
void addField(NetStatesField const& nsfield);
template<typename... Args>
void addFields(Args const&... fields);
// Removes all fields and resets back to the initial state.
void clearFields();
// Produces a digest of all the field types in this NetStates object
uint32_t fieldDigest() const;
// When sending, it is important *when* precisely updateStep is called in
// order not to lose data, the order *must* be:
// - updateStep(<increased step value>)
// - *do things that change data*
// - *write deltas*
// - repeat.
// No data should be set in between writing a delta and incrementing step, or
// data will be lost to slaves. (Assuming no delta overlapping is done to
// prevent it).
//
// An exception will be thrown if newStep ever decreases in a call to
// updateStep, reset must be called instead.
//
// Returns true in the event that updating the step changed the value of any
// fields. This will only be true when interpolation is enabled.
bool updateStep(uint64_t newStep);
uint64_t currentStep() const;
// Start the NetStepStates object back from the initial step (will all the
// existing fields). All current field values are kept unchanged and
// interpolation data is cleared.
void resetStep();
// Enables interpolation mode. If interpolation mode is enabled, then all
// incoming states are marked with a floating target step value, and states
// are delayed until the interpolation step reaches the given step value for
// that state. This also enables optional interpolation smoothing on
// floating point values that looks ahead into the future to even out
// changes, and can also optionally extrapolate into the future if no data is
// available.
//
// While interpolating, calling a set will discard any held interpolation
// data even into the future.
void enableInterpolation(uint64_t extrapolationSteps = 0);
void disableInterpolation();
bool interpolationEnabled() const;
// Returns true if there is changed data since this timestep, and writeDelta
// called with this step would write data.
bool hasDelta(uint64_t fromStep) const;
// Write all the state changes that have happened since (and including)
// fromStep. The normal way to use this would be each call to writeDelta
// should be passed the step at the time of the *last* call to writeDelta, +
// 1. writeDelta with fromStep = 0 writes all states, and is a larger
// version of writeFull(). If hasDelta(fromStep) returns false, this will
// still write some data as an end marker, it is best to call hasDelta first
// to see if any delta is needed.
void writeDelta(DataStream& ds, uint64_t fromStep) const;
// predictedDeltaStep is only necessary if interpolation is enabled,
// otherwise the delta is assumed to apply immediately. Will return true
// when reading this delta has changed the current state of any fields.
bool readDelta(DataStream& ds, double predictedDeltaStep = 0);
// Writes / reads all values regardless of changed state. Usually a slight
// network improvement over writeDelta(0), as it does not need to prefix
// every field with an index. Additionall, first writes the 4 byte field
// digest to the full state, so readFull also acts as a type consistency
// check.
void writeFull(DataStream& ds) const;
void readFull(DataStream& ds, double predictedDeltaStep = 0);
// Write and read deltas directly to a byte array. Uses a slightly different
// binary format that relies on the size of the array packet, so cannot be
// mixed with normal writes and reads.
ByteArray writeDeltaPacket(uint64_t fromStep) const;
bool readDeltaPacket(ByteArray packet, double predictedDeltaStep = 0);
ByteArray writeFullPacket() const;
void readFullPacket(ByteArray packet, double predictedDeltaStep = 0);
// Equivalent to reading a blank delta state, simply lets receiver know that
// no delta is needed for up to this timestep, so no extrapolation is
// required. Not required if interpolation is not enabled or is enabled with
// zero extrapolation steps.
void interpolationHeartbeat(double predictedDeltaStep = 0);
private:
DynamicArray<NetStatesDetail::FieldPtr> m_fields;
uint64_t m_currentStep;
bool m_interpolationEnabled;
uint64_t m_extrapolationSteps;
};
// NetStepStates with no concept of a step, only sends values that have been
// changed between writes. Only works for writing to a single receiver.
class NetSyncStates : private NetStepStates {
public:
NetSyncStates();
template<typename... Args>
NetSyncStates(Args const&... fields);
bool hasDelta() const;
void writeDelta(DataStream& ds);
bool readDelta(DataStream& ds);
ByteArray writeDeltaPacket();
bool readDeltaPacket(ByteArray packet);
void reset();
using NetStepStates::addField;
using NetStepStates::clearFields;
using NetStepStates::fieldDigest;
};
namespace NetStatesDetail {
// Values as they are sent across the wire, converted to their compressed or
// truncated transmission format.
typedef Any<int8_t, uint8_t, int16_t, uint16_t, int64_t, uint64_t, float, double, bool, shared_ptr<void>> TransmissionValue;
enum class TransmissionType : uint8_t {
Int8,
UInt8,
Int16,
UInt16,
VarInt,
VarUInt,
Size,
Float,
Double,
NFloat8,
NFloat16,
Fixed8,
Fixed16,
VarFixed,
Bool,
Event,
Generic
};
struct TypeInfo {
TransmissionType type;
// If this is a fixed point float, this is its fixed point base
Maybe<double> fixedPointBase;
// If the type is Data, this holds the reader / writer for it.
shared_ptr<GenericSerializer> genericSerializer;
// If this is any sort of floating type, then if value interpolation is
// enabled this will hold the interpolator
NetStatesInterpolator interpolator;
Value valueFromTransmission(TransmissionValue const& transmission) const;
TransmissionValue transmissionFromValue(Value const& value) const;
};
class Field {
public:
Field(TypeInfo typeInfo, Value value);
TypeInfo const& typeInfo() const;
int64_t getInt() const;
void setInt(int64_t value);
uint64_t getUInt() const;
void setUInt(uint64_t value);
size_t getSize() const;
void setSize(size_t value);
double getFloat() const;
void setFloat(double value);
void setFloatInterpolator(NetStatesInterpolator interpolator);
bool getBool() const;
void setBool(bool value);
void triggerEvent();
uint64_t pullOccurrences();
void ignoreOccurrences();
template<typename T>
T const& getGeneric() const;
template<typename T>
void setGeneric(T const& data);
template<typename T>
void pushGeneric(T data);
template<typename T, typename Mutator>
void updateGeneric(Mutator&& mutator);
bool pullUpdated();
// Returns true if field value may have changed
bool updateStep(uint64_t step);
void resetStep();
bool hasDelta(uint64_t fromStep) const;
bool readField(DataStream& ds, double interpolationStep);
void writeField(DataStream& ds) const;
void enableInterpolation(uint64_t extrapolationSteps);
void disableInterpolation();
void interpolationHeartbeat(double predictedDeltaStep);
private:
bool updateValueFromTransmission();
// Marks the value as updated by setting the unpulledUpdate flag and the
// transmissionDirty flag
void markValueUpdated();
// To support fast setting of values, transmission values are updated
// lazily. Once the transmissionDirty flag is set, call
// freshenTransmission() to (IF the transmission value has changed) update
// the latest transmission and latest transmission step from the current
// value.
void freshenTransmission();
TypeInfo m_typeInfo;
uint64_t m_currentStep;
Value m_value;
bool m_unpulledUpdate;
uint64_t m_pulledOccurrences;
bool m_transmissionDirty;
TransmissionValue m_latestTransmission;
uint64_t m_latestTransmissionStep;
Maybe<StepStream<TransmissionValue>> m_interpolatedTransmissions;
};
}
template<typename T>
NetStatesData<T>::NetStatesData(T initialValue)
: NetStatesData(
[](DataStream& ds, T& t) {
ds >> t;
},
[](DataStream& ds, T const& t) {
ds << t;
}, move(initialValue)) {}
template<typename T>
NetStatesData<T>::NetStatesData(function<void(DataStream&, T&)> reader, function<void(DataStream&, T const&)> writer, T initialValue)
: NetStatesField(NetStatesDetail::TypeInfo{
NetStatesDetail::TransmissionType::Generic, {},
make_shared<SerializerImpl>(move(reader), move(writer)), {}
}, shared_ptr<void>(make_shared<T>(move(initialValue)))) {}
template<typename T>
T const& NetStatesData<T>::get() const {
return m_field->template getGeneric<T>();
}
template<typename T>
void NetStatesData<T>::set(T const& value) {
m_field->template setGeneric<T>(value);
}
template<typename T>
void NetStatesData<T>::push(T value) {
m_field->template pushGeneric<T>(move(value));
}
template<typename T>
template<typename Mutator>
void NetStatesData<T>::update(Mutator&& mutator) {
m_field->template updateGeneric<T>(forward<Mutator>(mutator));
}
template<typename T>
NetStatesData<T>::SerializerImpl::SerializerImpl(function<void(DataStream&, T&)> reader, function<void(DataStream&, T const&)> writer)
: reader(move(reader)), writer(move(writer)) {}
template<typename T>
shared_ptr<void> NetStatesData<T>::SerializerImpl::read(DataStream& ds) const {
auto data = make_shared<T>();
reader(ds, *data);
return data;
}
template<typename T>
void NetStatesData<T>::SerializerImpl::write(DataStream& ds, shared_ptr<void> const& data) const {
writer(ds, *(T*)data.get());
}
template<typename T>
NetStatesEnum<T>::NetStatesEnum(T initialValue)
: NetStatesField(
NetStatesDetail::TypeInfo{NetStatesDetail::TransmissionType::VarInt, {}, {}, {}},
(int64_t)initialValue) {}
template<typename T>
T NetStatesEnum<T>::get() const {
return (T)m_field->getInt();
}
template<typename T>
void NetStatesEnum<T>::set(T value) {
return m_field->setInt((int64_t)value);
}
template<typename... Args>
NetStepStates::NetStepStates(Args const&... fields)
: NetStepStates() {
addFields(fields...);
}
template<typename... Args>
void NetStepStates::addFields(Args const&... fields) {
callFunctionVariadic([this](NetStatesField const& field) {
addField(field);
}, fields...);
}
template<typename... Args>
NetSyncStates::NetSyncStates(Args const&... fields)
: NetSyncStates() {
addFields(fields...);
}
template<typename T>
T const& NetStatesDetail::Field::getGeneric() const {
starAssert(m_value.get<shared_ptr<void>>().get() != nullptr);
return *(T const*)m_value.get<shared_ptr<void>>().get();
}
template<typename T>
void NetStatesDetail::Field::setGeneric(T const& data) {
auto& p = m_value.get<shared_ptr<void>>();
if (!(*(T*)p.get() == data)) {
*(T*)p.get() = data;
markValueUpdated();
}
}
template<typename T>
void NetStatesDetail::Field::pushGeneric(T data) {
if (m_value) {
auto& p = m_value.get<shared_ptr<void>>();
*(T*)p.get() = move(data);
} else {
m_value = shared_ptr<void>(make_shared<T>(move(data)));
}
markValueUpdated();
}
template<typename T, typename Mutator>
void NetStatesDetail::Field::updateGeneric(Mutator&& mutator) {
auto const& p = m_value.get<shared_ptr<void>>();
if (mutator(*(T*)p.get()))
markValueUpdated();
}
}
#endif

View file

@ -0,0 +1,645 @@
#include "StarNetTransmitters.hpp"
#include "StarIterator.hpp"
namespace Star {
NetVersionSender::NetVersionSender() {
m_currentVersion = 0;
}
NetVersionSender::NetVersionSender(NetSchema const& schema)
: NetVersionSender() {
initialize(schema);
}
void NetVersionSender::initialize(NetSchema const& schema) {
if (!schema.isFinalized())
throw NetException("schema unfinalized in NetVersionSender::initialize");
m_currentVersion = 0;
m_valuesByName.clear();
m_values.clear();
m_schema = schema;
for (auto id : m_schema.ids()) {
ValuePtr value = std::make_shared<Value>();
value->lastUpdated = m_currentVersion;
// Initialize with default values for each NetType, so we can error if a
// mismatched set was called.
auto const& fieldInfo = m_schema.fieldInfo(id);
if (fieldInfo.fixedPointBase != 0.0) {
value->data.set<double>(0.0);
} else if (fieldInfo.type == NetType::Event) {
value->data.set<uint64_t>(0);
} else if (netTypeIsSignedInt(fieldInfo.type)) {
value->data.set<int64_t>(0);
} else if (netTypeIsUnsignedInt(fieldInfo.type)) {
value->data.set<uint64_t>(0);
} else if (fieldInfo.type == NetType::Size) {
value->data.set<size_t>(NPos);
} else if (netTypeIsFloat(fieldInfo.type)) {
value->data.set<double>(0.0);
} else if (fieldInfo.type == NetType::Bool) {
value->data.set<bool>(false);
} else if (fieldInfo.type == NetType::String) {
value->data.set<String>(String());
} else if (fieldInfo.type == NetType::Data) {
value->data.set<ByteArray>(ByteArray());
} else if (fieldInfo.type == NetType::Variant) {
value->data.set<Variant>(Variant());
}
m_values.insert(id, value);
m_valuesByName.insert(m_schema.name(id), value);
}
}
NetSchema const& NetVersionSender::schema() const {
return m_schema;
}
void NetVersionSender::resetVersion() {
m_currentVersion = 0;
for (auto& pair : m_values)
pair.second->lastUpdated = m_currentVersion;
}
void NetVersionSender::incrementVersion(uint64_t version) {
if (version < m_currentVersion)
throw NetException("Version reversed in NetVersionSender::incrementVersion");
m_currentVersion = version;
}
uint64_t NetVersionSender::currentVersion() const {
return m_currentVersion;
}
void NetVersionSender::triggerEvent(StringRef name) {
auto const& value = getValue(name);
if (!value->data.is<uint64_t>())
throw NetException("NetSender type mismatch in triggerEvent");
value->data.set<uint64_t>(value->data.get<uint64_t>() + 1);
value->lastUpdated = m_currentVersion;
}
void NetVersionSender::setInt(StringRef name, int64_t v) {
auto const& value = getValue(name);
if (!value->data.is<int64_t>())
throw NetException("NetSender type mismatch in setInt");
if (value->data.cget<int64_t>() != v) {
value->lastUpdated = m_currentVersion;
value->data.set<int64_t>(v);
}
}
void NetVersionSender::setUInt(StringRef name, uint64_t v) {
auto const& value = getValue(name);
if (!value->data.is<uint64_t>())
throw NetException("NetSender type mismatch in setUInt");
if (value->data.cget<uint64_t>() != v) {
value->lastUpdated = m_currentVersion;
value->data.set<uint64_t>(v);
}
}
void NetVersionSender::setSize(StringRef name, size_t v) {
auto const& value = getValue(name);
if (!value->data.is<size_t>())
throw NetException("NetSender type mismatch in setSize");
if (value->data.cget<size_t>() != v) {
value->lastUpdated = m_currentVersion;
value->data.set<size_t>(v);
}
}
void NetVersionSender::setFloat(StringRef name, double v) {
auto const& value = getValue(name);
if (!value->data.is<double>())
throw NetException("NetSender type mismatch in setFloat");
// TODO: For right now does a straight != comparison, should we include a
// tolerance setting?
if (value->data.cget<double>() != v) {
value->lastUpdated = m_currentVersion;
value->data.set<double>(v);
}
}
void NetVersionSender::setBool(StringRef name, bool v) {
auto const& value = getValue(name);
if (!value->data.is<bool>())
throw NetException("NetSender type mismatch in setBool");
if (value->data.cget<bool>() != v) {
value->lastUpdated = m_currentVersion;
value->data.set<bool>(v);
}
}
void NetVersionSender::setString(StringRef name, String const& v) {
auto const& value = getValue(name);
if (!value->data.is<String>())
throw NetException("NetSender type mismatch in setString");
if (value->data.cget<String>() != v) {
value->lastUpdated = m_currentVersion;
value->data.set<String>(v);
}
}
void NetVersionSender::setData(StringRef name, ByteArray const& v) {
auto const& value = getValue(name);
if (!value->data.is<ByteArray>())
throw NetException("NetSender type mismatch in setData");
if (value->data.cget<ByteArray>() != v) {
value->lastUpdated = m_currentVersion;
value->data.set<ByteArray>(v);
}
}
void NetVersionSender::setVariant(StringRef name, Variant const& v) {
auto const& value = getValue(name);
if (!value->data.is<Variant>())
throw NetException("NetSender type mismatch in setVariant");
if (value->data.cget<Variant>() != v) {
value->lastUpdated = m_currentVersion;
value->data.set<Variant>(v);
}
}
size_t NetVersionSender::writeFull(DataStream& ds) const {
size_t bytesWritten = 0;
for (auto id : m_schema.ids()) {
auto const& value = m_values.value(id);
auto const& field = m_schema.fieldInfo(id);
bytesWritten += writeValue(ds, field, value);
}
return bytesWritten;
}
ByteArray NetVersionSender::writeFull() const {
DataStreamBuffer buffer;
writeFull(buffer);
return buffer.data();
}
size_t NetVersionSender::writeDelta(DataStream& ds, uint64_t sinceVersion) const {
size_t bytesWritten = 0;
for (auto const& pair : m_values) {
auto const& value = pair.second;
if (value->lastUpdated < sinceVersion)
continue;
auto const& field = m_schema.fieldInfo(pair.first);
bytesWritten += ds.writeVlqU(pair.first);
bytesWritten += writeValue(ds, field, value);
}
return bytesWritten;
}
ByteArray NetVersionSender::writeDelta(uint64_t sinceVersion) const {
DataStreamBuffer buffer;
writeDelta(buffer, sinceVersion);
return buffer.data();
}
NetVersionSender::ValuePtr const& NetVersionSender::getValue(StringRef name) const {
auto i = m_valuesByName.find(name);
if (i == m_valuesByName.end())
throw NetException(strf("No such NetSender state named '%s'", name));
return i->second;
}
size_t NetVersionSender::writeValue(DataStream& ds, NetFieldInfo const& fieldInfo, ValuePtr const& value) const {
StreamOffset pos = ds.pos();
DataPoint data;
if (fieldInfo.fixedPointBase != 0.0) {
if (netTypeIsSignedInt(fieldInfo.type))
data.set<int64_t>(value->data.get<double>() / fieldInfo.fixedPointBase);
else
data.set<uint64_t>(value->data.get<double>() / fieldInfo.fixedPointBase);
} else {
data = value->data;
}
if (fieldInfo.type == NetType::Event) {
ds.writeVlqU(data.get<uint64_t>());
} else if (fieldInfo.type == NetType::Int8) {
ds.write<int8_t>(data.get<int64_t>());
} else if (fieldInfo.type == NetType::UInt8) {
ds.write<uint8_t>(data.get<uint64_t>());
} else if (fieldInfo.type == NetType::Int16) {
ds.write<int16_t>(data.get<int64_t>());
} else if (fieldInfo.type == NetType::UInt16) {
ds.write<uint16_t>(data.get<uint64_t>());
} else if (fieldInfo.type == NetType::VarInt) {
ds.writeVlqI(data.get<int64_t>());
} else if (fieldInfo.type == NetType::VarUInt) {
ds.writeVlqU(data.get<uint64_t>());
} else if (fieldInfo.type == NetType::Size) {
size_t v = data.get<size_t>();
ds.writeVlqU(v == NPos ? 0 : v + 1);
} else if (fieldInfo.type == NetType::Float) {
ds.write<float>(data.get<double>());
} else if (fieldInfo.type == NetType::Double) {
ds.write<double>(data.get<double>());
} else if (fieldInfo.type == NetType::NFloat8) {
ds.write<uint8_t>(std::round(data.get<double>() * 255.0f));
} else if (fieldInfo.type == NetType::NFloat16) {
ds.write<uint16_t>(std::round(data.get<double>() * 65535.0f));
} else if (fieldInfo.type == NetType::Bool) {
ds.write<bool>(data.get<bool>());
} else if (fieldInfo.type == NetType::String) {
ds.write(data.get<String>());
} else if (fieldInfo.type == NetType::Data) {
ds.write(data.get<ByteArray>());
} else if (fieldInfo.type == NetType::Variant) {
ds.write(data.get<Variant>());
}
return ds.pos() - pos;
}
NetSyncSender::NetSyncSender() {}
NetSyncSender::NetSyncSender(NetSchema const& schema) {
initialize(schema);
}
size_t NetSyncSender::writeDelta(DataStream& ds) {
size_t size = NetVersionSender::writeDelta(ds, currentVersion());
// Increment the version on write so we only write newly written states.
incrementVersion(currentVersion() + 1);
return size;
}
size_t NetSyncSender::writeFull(DataStream& ds) {
size_t size = NetVersionSender::writeFull(ds);
// Increment the version on write so we only write newly written states.
incrementVersion(currentVersion() + 1);
return size;
}
ByteArray NetSyncSender::writeFull() {
DataStreamBuffer buffer;
writeFull(buffer);
return buffer.data();
}
ByteArray NetSyncSender::writeDelta() {
DataStreamBuffer buffer;
writeDelta(buffer);
return buffer.data();
}
NetReceiver::NetReceiver()
: m_interpolationEnabled(false), m_interpolationStep(0.0) {}
NetReceiver::NetReceiver(NetSchema const& schema)
: NetReceiver() {
initialize(schema);
}
void NetReceiver::initialize(NetSchema const& schema) {
if (!schema.isFinalized())
throw NetException("schema unfinalized in NetReceiver::initialize");
m_valuesByName.clear();
m_values.clear();
m_schema = schema;
for (auto id : m_schema.ids()) {
ValuePtr value = std::make_shared<Value>();
value->needsForwarding = false;
value->exactUpdated = false;
// Initialize with default values because NetSender assumes these as the
// starting values without sending any data
auto const& fieldInfo = m_schema.fieldInfo(id);
if (fieldInfo.fixedPointBase != 0.0) {
value->latestData = 0.0;
} else if (fieldInfo.type == NetType::Event) {
value->latestData = (uint64_t)0;
} else if (netTypeIsSignedInt(fieldInfo.type)) {
value->latestData = (int64_t)0;
} else if (netTypeIsUnsignedInt(fieldInfo.type)) {
value->latestData = (uint64_t)0;
} else if (fieldInfo.type == NetType::Size) {
value->latestData = (size_t)NPos;
} else if (netTypeIsFloat(fieldInfo.type)) {
value->latestData = 0.0;
} else if (fieldInfo.type == NetType::Bool) {
value->latestData = false;
} else if (fieldInfo.type == NetType::String) {
value->latestData = String();
} else if (fieldInfo.type == NetType::Data) {
value->latestData = ByteArray();
} else if (fieldInfo.type == NetType::Variant) {
value->latestData = Variant();
}
value->exactData = value->latestData;
value->exactUpdated = false;
if (m_interpolationEnabled) {
value->stream.reset();
value->stream.appendDataPoint(m_interpolationStep, value->latestData);
}
m_values.insert(id, value);
m_valuesByName.insert(m_schema.name(id), value);
}
}
NetSchema const& NetReceiver::schema() const {
return m_schema;
}
void NetReceiver::enableInterpolation(double startStep, double extrapolation) {
m_interpolationEnabled = true;
m_interpolationStep = startStep;
for (auto const& pair : m_values) {
pair.second->stream.reset();
pair.second->stream.setExtrapolation(extrapolation);
pair.second->stream.appendDataPoint(startStep, pair.second->latestData);
pair.second->stream.setStep(startStep);
if (pair.second->exactData != pair.second->latestData) {
pair.second->exactData = pair.second->latestData;
pair.second->exactUpdated = true;
}
}
}
void NetReceiver::disableInterpolation() {
m_interpolationEnabled = false;
m_interpolationStep = 0.0;
for (auto const& pair : m_values) {
pair.second->stream.reset();
if (pair.second->exactData != pair.second->latestData) {
pair.second->exactData = pair.second->latestData;
pair.second->exactUpdated = true;
}
}
}
void NetReceiver::setInterpolationDiffFunction(StringRef name, DifferenceFunction diffFunction) {
getValue(name)->diffFunction = diffFunction;
}
void NetReceiver::setInterpolationStep(double step) {
if (!m_interpolationEnabled)
return;
m_interpolationStep = step;
for (auto const& pair : m_values) {
pair.second->stream.setStep(step);
auto data = pair.second->stream.exact();
if (data != pair.second->exactData) {
pair.second->exactData = data;
pair.second->exactUpdated = true;
}
}
}
void NetReceiver::readFull(DataStream& ds, size_t dataSize) {
int bytesLeft = dataSize;
for (unsigned id : m_schema.ids()) {
auto const& field = m_schema.fieldInfo(id);
bytesLeft -= readValue(ds, field, m_values[id], m_interpolationStep);
if (bytesLeft < 0)
throw NetException("Read past end of dataSize in NetReceiver::readFull!");
}
if (bytesLeft != 0)
throw NetException("Extra data at end of NetReceiver::readFull!");
}
void NetReceiver::readFull(ByteArray const& fullState) {
DataStreamBuffer buffer(fullState);
readFull(buffer, buffer.size());
}
void NetReceiver::readDelta(DataStream& ds, size_t dataSize, double interpolationStep) {
int bytesLeft = dataSize;
while (bytesLeft > 0) {
uint64_t id;
bytesLeft -= ds.readVlqU(id);
bytesLeft -= readValue(ds, m_schema.fieldInfo(id), m_values[id], interpolationStep);
// TODO: This is pretty terrible, we shouldn't read extra *then* fail
if (bytesLeft < 0)
throw NetException("Read past end of dataSize in NetReceiver::readDelta!");
}
}
void NetReceiver::readDelta(ByteArray const& deltaState, double interpolationStep) {
DataStreamBuffer buffer(deltaState);
readDelta(buffer, buffer.size(), interpolationStep);
}
void NetReceiver::interpolationHeartbeat(double interpolationStep) {
if (m_interpolationEnabled) {
for (auto const& pair : m_values)
pair.second->stream.heartbeat(interpolationStep);
}
}
bool NetReceiver::updated(StringRef name) const {
return getValue(name)->exactUpdated;
}
bool NetReceiver::eventOccurred(StringRef eventName) {
auto const& value = getValue(eventName);
if (value->exactUpdated) {
value->exactUpdated = false;
return true;
} else {
return false;
}
}
int64_t NetReceiver::getInt(StringRef name) {
auto const& value = getValue(name);
value->exactUpdated = false;
return value->exactData.cget<int64_t>();
}
uint64_t NetReceiver::getUInt(StringRef name) {
auto const& value = getValue(name);
value->exactUpdated = false;
return value->exactData.cget<uint64_t>();
}
size_t NetReceiver::getSize(StringRef name) {
auto const& value = getValue(name);
value->exactUpdated = false;
return value->exactData.cget<size_t>();
}
double NetReceiver::getFloat(StringRef name, bool interpolateIfAvailable) {
auto const& value = getValue(name);
value->exactUpdated = false;
if (m_interpolationEnabled && interpolateIfAvailable) {
auto weighting = value->stream.weighting();
auto pointMin = weighting.pointMin.cget<double>();
auto pointMax = weighting.pointMax.cget<double>();
if (value->diffFunction)
return pointMin + value->diffFunction(pointMax, pointMin) * weighting.offset;
else
return pointMin + (pointMax - pointMin) * weighting.offset;
} else {
return value->exactData.cget<double>();
}
}
bool NetReceiver::getBool(StringRef name) {
auto const& value = getValue(name);
value->exactUpdated = false;
return value->exactData.cget<bool>();
}
String NetReceiver::getString(StringRef name) {
auto const& value = getValue(name);
value->exactUpdated = false;
return value->exactData.cget<String>();
}
ByteArray NetReceiver::getData(StringRef name) {
auto const& value = getValue(name);
value->exactUpdated = false;
return value->exactData.cget<ByteArray>();
}
Variant NetReceiver::getVariant(StringRef name) {
auto const& value = getValue(name);
value->exactUpdated = false;
return value->exactData.cget<Variant>();
}
void NetReceiver::forward(NetVersionSender& sender, bool forceAll) {
for (auto id : m_schema.ids()) {
auto const& value = m_values.get(id);
auto const& fieldInfo = m_schema.fieldInfo(id);
if (!forceAll && !value->needsForwarding)
continue;
if (fieldInfo.fixedPointBase != 0.0) {
sender.setFloat(fieldInfo.name, value->latestData.cget<double>());
} else if (fieldInfo.type == NetType::Event) {
if (value->needsForwarding)
sender.triggerEvent(fieldInfo.name);
} else if (netTypeIsSignedInt(fieldInfo.type)) {
sender.setInt(fieldInfo.name, value->latestData.cget<int64_t>());
} else if (netTypeIsUnsignedInt(fieldInfo.type)) {
sender.setUInt(fieldInfo.name, value->latestData.cget<uint64_t>());
} else if (fieldInfo.type == NetType::Size) {
sender.setSize(fieldInfo.name, value->latestData.cget<size_t>());
} else if (netTypeIsFloat(fieldInfo.type)) {
sender.setFloat(fieldInfo.name, value->latestData.cget<double>());
} else if (fieldInfo.type == NetType::Bool) {
sender.setBool(fieldInfo.name, value->latestData.cget<bool>());
} else if (fieldInfo.type == NetType::String) {
sender.setString(fieldInfo.name, value->latestData.cget<String>());
} else if (fieldInfo.type == NetType::Data) {
sender.setData(fieldInfo.name, value->latestData.cget<ByteArray>());
} else if (fieldInfo.type == NetType::Variant) {
sender.setVariant(fieldInfo.name, value->latestData.cget<Variant>());
}
value->needsForwarding = false;
}
}
NetReceiver::ValuePtr const& NetReceiver::getValue(StringRef name) const {
auto i = m_valuesByName.find(name);
if (i == m_valuesByName.end())
throw NetException(strf("No such NetReceiver state named '%s'", name));
return i->second;
}
size_t NetReceiver::readValue(DataStream& ds, NetFieldInfo const& fieldInfo, ValuePtr const& value, double step) {
StreamOffset pos = ds.pos();
DataPoint dataPoint;
if (fieldInfo.type == NetType::Event) {
dataPoint.set<uint64_t>(ds.readVlqU());
} else if (fieldInfo.type == NetType::Int8) {
dataPoint.set<int64_t>(ds.read<int8_t>());
} else if (fieldInfo.type == NetType::UInt8) {
dataPoint.set<uint64_t>(ds.read<uint8_t>());
} else if (fieldInfo.type == NetType::Int16) {
dataPoint.set<int64_t>(ds.read<int16_t>());
} else if (fieldInfo.type == NetType::UInt16) {
dataPoint.set<uint64_t>(ds.read<uint16_t>());
} else if (fieldInfo.type == NetType::VarInt) {
dataPoint.set<int64_t>(ds.readVlqI());
} else if (fieldInfo.type == NetType::VarUInt) {
dataPoint.set<uint64_t>(ds.readVlqU());
} else if (fieldInfo.type == NetType::Size) {
auto size = ds.readVlqU();
dataPoint.set<size_t>(size == 0 ? NPos : size - 1);
} else if (fieldInfo.type == NetType::Float) {
dataPoint.set<double>(ds.read<float>());
} else if (fieldInfo.type == NetType::NFloat8) {
dataPoint.set<double>(ds.read<uint8_t>() / 255.0f);
} else if (fieldInfo.type == NetType::NFloat16) {
dataPoint.set<double>(ds.read<uint16_t>() / 65535.0f);
} else if (fieldInfo.type == NetType::Double) {
dataPoint.set<double>(ds.read<double>());
} else if (fieldInfo.type == NetType::Bool) {
dataPoint.set<bool>(ds.read<bool>());
} else if (fieldInfo.type == NetType::String) {
dataPoint.set<String>(ds.read<String>());
} else if (fieldInfo.type == NetType::Data) {
dataPoint.set<ByteArray>(ds.read<ByteArray>());
} else if (fieldInfo.type == NetType::Variant) {
dataPoint.set<Variant>(ds.read<Variant>());
}
if (fieldInfo.fixedPointBase != 0.0) {
if (netTypeIsSignedInt(fieldInfo.type))
dataPoint.set<double>(dataPoint.cget<int64_t>() * fieldInfo.fixedPointBase);
else
dataPoint.set<double>(dataPoint.cget<uint64_t>() * fieldInfo.fixedPointBase);
}
value->latestData = dataPoint;
value->needsForwarding = true;
if (m_interpolationEnabled) {
value->stream.appendDataPoint(step, dataPoint);
auto data = value->stream.exact();
if (value->exactData != data) {
value->exactData = data;
value->exactUpdated = true;
}
} else {
if (value->exactData != value->latestData) {
value->exactData = value->latestData;
value->exactUpdated = true;
}
}
return ds.pos() - pos;
}
}

View file

@ -0,0 +1,231 @@
#ifndef STAR_NET_TRANSMITTERS_HPP
#define STAR_NET_TRANSMITTERS_HPP
#include "StarNetSchema.hpp"
#include "StarDataStream.hpp"
#include "StarStepInterpolation.hpp"
#include "StarVariant.hpp"
#include "StarAny.hpp"
namespace Star {
// Send states using delta coding based on a always increasing version. The
// version is never sent over the wire, rather it is used as a way to track
// versions of states. Useful to send updates to several receivers. It must
// be used in the following way to avoid lost data:
//
// 1. Set any changed data.
// 2. Do any number of writeDelta calls
// 3. Set new version
// 4. Repeat...
//
// writeFull can be called at any time, but it is generally used on the 0th
// version, so that writeDelta(0) is never called. The order is then:
//
// 1. Set any initial data.
// 2. Do writeFull call.
// 3. Set new version > 0
// 4. Enter the loop above.
class NetVersionSender {
public:
NetVersionSender();
NetVersionSender(NetSchema const& schema);
NetVersionSender(NetVersionSender const& sender) = delete;
NetVersionSender& operator=(NetVersionSender const& sender) = delete;
void initialize(NetSchema const& schema);
NetSchema const& schema() const;
// Resets version, clears events, keeps all current values.
void resetVersion();
// After initialization / reset, version must always go forward.
void incrementVersion(uint64_t version);
uint64_t currentVersion() const;
// Marks this event as having occurred
void triggerEvent(StringRef eventName);
// The correct set function must be called for each data type. Signed
// integral values must be set with setInt, unsigned integral values with
// setUInt. Any floating point type *or* an integral type if marked as a
// "fixed point value" must be set with setFloat. Bools must be set with
// setBool, and data values must be set with setData. Anything else results
// in an exception.
void setInt(StringRef name, int64_t i);
void setUInt(StringRef name, uint64_t u);
void setSize(StringRef name, size_t s);
void setFloat(StringRef name, double d);
void setBool(StringRef name, bool b);
void setString(StringRef name, String const& str);
void setData(StringRef name, ByteArray const& data);
void setVariant(StringRef name, Variant const& data);
// Write full set of states. Returns bytes written.
size_t writeFull(DataStream& ds) const;
ByteArray writeFull() const;
// Write all the state changes that have happened since (and including)
// sinceVersion. Returns bytes written. The normal way to use this would be
// each call to writeDelta should be passed the version at the time of the
// *last* call to writeDelta, + 1. writeDelta with sinceVersion = 0 writes
// all states, and is a larger version of writeFull().
size_t writeDelta(DataStream& ds, uint64_t sinceVersion) const;
ByteArray writeDelta(uint64_t sinceVersion) const;
private:
typedef AnyOf<int64_t, uint64_t, size_t, double, bool, ByteArray, String, Variant> DataPoint;
struct Value {
uint64_t lastUpdated;
DataPoint data;
};
typedef std::shared_ptr<Value> ValuePtr;
ValuePtr const& getValue(StringRef name) const;
size_t writeValue(DataStream& ds, NetFieldInfo const& fieldInfo, ValuePtr const& value) const;
NetSchema m_schema;
uint64_t m_currentVersion;
Map<unsigned, ValuePtr> m_values;
StringMap<ValuePtr> m_valuesByName;
};
// Like NetVersionSender, except no concept of version, just sends only when
// values have been changed in between writes. Only works when writing to a
// single receiver. Is binary compatible with NetVersionSender.
class NetSyncSender: public NetVersionSender {
public:
NetSyncSender();
NetSyncSender(NetSchema const& schema);
// Write all of the states.
size_t writeFull(DataStream& ds);
// Write all the state changes that have occurred. It is possible to use
// this without ever using writeFull (though it may transfer slightly more
// data the first time).
size_t writeDelta(DataStream& ds);
ByteArray writeFull();
ByteArray writeDelta();
private:
using NetVersionSender::incrementVersion;
using NetVersionSender::currentVersion;
using NetVersionSender::writeFull;
using NetVersionSender::writeDelta;
};
// Receive states using delta coding and optional smoothing from either a
// NetVersionSender or a NetSyncSender
class NetReceiver {
public:
typedef std::function<double(double, double)> DifferenceFunction;
NetReceiver();
NetReceiver(NetSchema const& schema);
NetReceiver(NetReceiver const& receiver) = delete;
NetReceiver& operator=(NetReceiver const& receiver) = delete;
void initialize(NetSchema const& schema);
NetSchema const& schema() const;
// Enables interpolation mode for this NetReceiver. If interpolation mode is
// enabled, then all states are marked with a floating step value, and states
// are delayed until the interpolation step reaches the given step value for
// that state. This also enables optional smoothing on floating point values
// that looks ahead into the future to even out changes, and can also
// optionally extrapolate into the future if no data is available.
void enableInterpolation(double startStep, double extrapolation = 0.0);
void disableInterpolation();
// For certain values, simple subtraction between two values may not be
// appropriate for finding the difference between them (such as circular
// values). If provided, this function will be called instead.
void setInterpolationDiffFunction(StringRef name, DifferenceFunction diffFunction = DifferenceFunction());
// If using interpolation, should be called every step. Throws away old
// data, so if this is called with a future value then goes backwards, it may
// result in different behavior (the interpolated data will be locked to the
// highest time value given here).
void setInterpolationStep(double step);
// Read full state list sent by a NetVersionSender.
void readFull(DataStream& ds, size_t dataSize);
void readFull(ByteArray const& fullState);
// Read partial state list sent by a NetVersionSender. If interpolation is
// enabled, interpolationStep must be specified. interpolationStep is
// assumed to monotonically increase here, and even in the event that it goes
// backwards, the data provided most recently will still be treated as though
// it is the best, most recent data.
void readDelta(DataStream& ds, size_t dataSize, double interpolationStep = 0);
void readDelta(ByteArray const& deltaState, double interpolationStep = 0);
// If no data for this timestep is available, treat it as though no data has
// actually changed, thus preventing extrapolation. Only has an effect if
// interpolation is in use.
void interpolationHeartbeat(double interpolationStep);
// Has the current value of this field been read via get*? (Useful to avoid
// a costly method on state change.) Safe to ignore completely.
bool updated(StringRef name) const;
// Has the given even occurred since the last time eventOccurred was called
// for this event? Right now, events can only be triggered as fast as
// NetStates updates happen. Multiple calls to triggerEvent between updates
// will only result in this returning true once.
bool eventOccurred(StringRef eventName);
int64_t getInt(StringRef name);
uint64_t getUInt(StringRef name);
size_t getSize(StringRef name);
double getFloat(StringRef name, bool interpolateIfAvailable = true);
bool getBool(StringRef name);
String getString(StringRef name);
ByteArray getData(StringRef name);
Variant getVariant(StringRef name);
// Forward all states from a receiver to another sender. This is useful to
// tie one sender to multiple chained receivers. Data is forwarded
// immediately as it is received, not as it is reached via interpolation.
void forward(NetVersionSender& sender, bool forceAll = false);
private:
typedef AnyOf<int64_t, uint64_t, size_t, double, bool, ByteArray, String, Variant> DataPoint;
struct Value {
// Absolute Latest data.
DataPoint latestData;
bool needsForwarding;
// Best, most recent *exact* data point, without looking into the future.
DataPoint exactData;
bool exactUpdated;
// Interpolation stream
StepStream<DataPoint> stream;
DifferenceFunction diffFunction;
};
typedef std::shared_ptr<Value> ValuePtr;
ValuePtr const& getValue(StringRef name) const;
size_t readValue(DataStream& ds, NetFieldInfo const& fieldInfo, ValuePtr const& value, double step);
NetSchema m_schema;
Map<unsigned, ValuePtr> m_values;
StringMap<ValuePtr> m_valuesByName;
bool m_interpolationEnabled;
double m_interpolationStep;
};
}
#endif

View file

@ -0,0 +1,26 @@
#include "StarServerShell.hpp"
#include "StarUniverseServer.hpp"
#include "StarLua.hpp"
namespace Star {
ServerShell::ServerShell(UniverseServerWeakPtr universe)
: m_universe(universe) {}
void ServerShell::run() {
}
String ServerShell::help() const {
return "";
}
UniverseServerPtr ServerShell::universe() const {
if (auto universe = m_universe.lock()) {
return universe;
}
throw ServerShellException("Running universe not valid.");
}
}

View file

@ -0,0 +1,28 @@
#ifndef STAR_SERVER_SHELL_HPP
#define STAR_SERVER_SHELL_HPP
#include "StarException.hpp"
namespace Star {
STAR_EXCEPTION(ServerShellException, StarException);
STAR_CLASS(UniverseServer);
STAR_CLASS(LuaContext);
class ServerShell {
public:
ServerShell(UniverseServerWeakPtr universe);
void run();
private:
String help() const;
UniverseServerPtr universe() const;
UniverseServerWeakPtr m_universe;
LuaContextPtr m_shellContext;
};
}
#endif

View file

@ -0,0 +1,71 @@
#include "StarStreamingVideoInterface.hpp"
#include "StarGuiReader.hpp"
#include "StarRoot.hpp"
#include "StarStreamingVideoController.hpp"
#include "StarWrapLabelWidget.hpp"
#include "StarButtonWidget.hpp"
#include "StarLogging.hpp"
#include "StarAssets.hpp"
namespace Star {
StreamingVideoOptionsPane::StreamingVideoOptionsPane(StreamingVideoControllerPtr streamingVideoController) {
m_streamingVideoController = streamingVideoController;
GuiReader reader;
reader.registerCallback("close", [=](WidgetPtr) -> bool {hide(); return true;});
reader.registerCallback("btnStart", [=](WidgetPtr) -> bool {start(); return true;});
reader.registerCallback("btnStop", [=](WidgetPtr) -> bool {stop(); return true;});
auto assets = Root::singleton().assets();
reader.construct(assets->variant("/interface/windowconfig/streamingvideo.config:paneLayout"), this);
fetchChild<WrapLabelWidget>("lblErrors")->setText("");
fetchChild<WrapLabelWidget>("lblStatus")->setText("");
}
StreamingVideoOptionsPane::~StreamingVideoOptionsPane() {
}
void StreamingVideoOptionsPane::update() {
if (m_streamingVideoController->hasError())
fetchChild<WrapLabelWidget>("lblErrors")->setText(m_streamingVideoController->nextError());
if (m_streamingVideoController->hasStatus())
fetchChild<WrapLabelWidget>("lblStatus")->setText(m_streamingVideoController->nextStatus());
if (m_streamingVideoController->active()) {
fetchChild<ButtonWidget>("btnStart")->disable();
fetchChild<ButtonWidget>("btnStop")->enable();
} else {
fetchChild<ButtonWidget>("btnStart")->enable();
fetchChild<ButtonWidget>("btnStop")->disable();
}
}
void StreamingVideoOptionsPane::start() {
fetchChild<WrapLabelWidget>("lblErrors")->setText("");
fetchChild<WrapLabelWidget>("lblStatus")->setText("Start requested.");
try {
m_streamingVideoController->setStreamConfiguration(Root::singleton().assets()->variant("/streamingvideo.config"));
m_streamingVideoController->start();
}
catch (std::exception const& e) {
Logger::error("Error starting streamingvideo: %s", e.what());
fetchChild<WrapLabelWidget>("lblErrors")->setText(strf("Error starting streamingvideo: %s", e.what()));
}
}
void StreamingVideoOptionsPane::stop() {
fetchChild<WrapLabelWidget>("lblErrors")->setText("");
fetchChild<WrapLabelWidget>("lblStatus")->setText("Stop requested.");
try {
m_streamingVideoController->stop();
}
catch (std::exception const& e) {
Logger::error("Error stopping streamingvideo: %s", e.what());
fetchChild<WrapLabelWidget>("lblErrors")->setText(strf("Error stopping streamingvideo: %s", e.what()));
}
}
}

View file

@ -0,0 +1,27 @@
#ifndef STAR_STREAMINGVIDEO_INTERFACE_HPP
#define STAR_STREAMINGVIDEO_INTERFACE_HPP
#include "StarPane.hpp"
namespace Star {
STAR_CLASS(StreamingVideoController);
STAR_CLASS(StreamingVideoOptionsPane);
class StreamingVideoOptionsPane : public Pane {
public:
StreamingVideoOptionsPane(StreamingVideoControllerPtr streamingVideoController);
virtual ~StreamingVideoOptionsPane();
virtual void update() override;
private:
StreamingVideoControllerPtr m_streamingVideoController;
void start();
void stop();
};
}
#endif

View file

@ -0,0 +1,117 @@
#include "StarStringContainers.hpp"
#include <cctype>
#include "pcre.h"
namespace Star {
StringList::StringList()
: Base() {}
StringList::StringList(const Base& l)
: Base(l) {}
StringList::StringList(Base&& l)
: Base(std::move(l)) {}
StringList::StringList(size_t n, String::Char const* const * list) {
for (size_t i = 0; i < n; ++i)
append(String(list[i]));
}
StringList::StringList(size_t len, const String& s1)
: Base(len, s1) {
}
StringList::StringList(std::initializer_list<String> list)
: Base(list) {
}
bool StringList::contains(const String& s, String::CaseSensitivity cs) const {
for (const_iterator i = begin(); i != end(); ++i) {
if (s.compare(*i, cs) == 0)
return true;
}
return false;
}
StringList StringList::trimAll(const String& pattern) const {
StringList r;
for (auto const& s : *this)
r.append(s.trim(pattern));
return r;
}
String StringList::join(const String& separator) const {
String joinedString;
for (const_iterator i = begin(); i != end(); ++i) {
if (i != begin())
joinedString += separator;
joinedString += *i;
}
return joinedString;
}
StringList& StringList::append(String const& e) {
Base::append(e);
return *this;
}
StringList& StringList::append(String&& e) {
Base::append(std::move(e));
return *this;
}
StringList& StringList::prepend(String const& e) {
Base::prepend(e);
return *this;
}
StringList& StringList::prepend(String&& e) {
Base::prepend(std::move(e));
return *this;
}
StringList& StringList::remove(String const& e) {
Base::remove(e);
return *this;
}
StringList& StringList::erase(size_t index) {
Base::erase(index);
return *this;
}
StringList& StringList::erase(size_t begin, size_t end) {
Base::erase(begin, end);
return *this;
}
StringList& StringList::insert(size_t pos, String const& e) {
Base::insert(pos, e);
return *this;
}
StringList& StringList::set(size_t pos, String const& e) {
Base::set(pos, e);
return *this;
}
StringList StringList::slice(SliceIndex a, SliceIndex b, int i) const {
return Star::slice(*this, a, b, i);
}
std::ostream& operator<<(std::ostream& os, const StringList& list) {
os << "(";
for (auto i = list.begin(); i != list.end(); ++i) {
if (i != list.begin())
os << ", ";
os << '\'' << *i << '\'';
}
os << ")";
return os;
}
}

View file

@ -0,0 +1,698 @@
#ifndef STAR_STRING_CONTAINERS_HPP
#define STAR_STRING_CONTAINERS_HPP
#include "StarString.hpp"
#include "StarList.hpp"
#include "StarMap.hpp"
#include "StarSet.hpp"
#include "StarMaybe.hpp"
namespace Star {
class StringList : public List<String> {
public:
typedef List<String> Base;
typedef Base::iterator iterator;
typedef Base::const_iterator const_iterator;
typedef Base::value_type value_type;
template<typename Container>
static StringList from(Container const& m) {
return StringList(m.begin(), m.end());
}
StringList();
StringList(Base const& l);
StringList(Base&& l);
StringList(size_t len, String::Char const* const* list);
StringList(size_t len, String const& s1);
StringList(std::initializer_list<String> list);
template<typename InputIterator>
StringList(InputIterator beg, InputIterator end) : Base(beg, end) {}
bool contains(String const& s, String::CaseSensitivity cs = String::CaseSensitive) const;
StringList trimAll(String const& pattern = "") const;
String join(String const& separator = "") const;
StringList& append(String const& e);
StringList& append(String&& e);
StringList& prepend(String const& e);
StringList& prepend(String&& e);
template<typename Container>
StringList& appendAll(Container&& list);
template<typename Container>
StringList& prependAll(Container&& list);
StringList& remove(String const& e);
StringList& erase(size_t index);
StringList& erase(size_t begin, size_t end);
using Base::erase;
StringList& insert(size_t pos, String const& e);
template<typename Container>
StringList& insertAll(size_t pos, Container const& l);
using Base::insert;
StringList& set(size_t pos, String const& e);
StringList slice(SliceIndex a = SliceIndex(), SliceIndex b = SliceIndex(), int i = 1) const;
};
std::ostream& operator<<(std::ostream& os, StringList const& list);
// A type that holds a reference to String/std::string/char*, or an actual held
// String. The lifetime of the class must not be longer than that of a normal
// const&. Is efficient for both lvalue and rvalue references. Internally to
// StringMap, hold() will be called to ensure that the ref holds an actual
// copy of the string.
class StringRef {
public:
// All lvalue constructors make the StringRef hold only the pointer, all
// rvalue constructors cause StringRef to hold the actual String.
StringRef(String const& s);
StringRef(String&& s);
StringRef(std::string const& s);
StringRef(std::string&& s);
StringRef(char const* p);
// No copy constructor is provided to make tracking ownership sane.
StringRef(StringRef&& sr);
StringRef& operator=(StringRef&& sr);
char const* ptr() const;
// Cause this StringRef to always hold a String, not just a ptr. Will be
// cheap if StringRef was constructed with an rvalue.
void hold();
// Gets a const& to the held String if it is held, UB otherwise.
String const& held() const;
bool operator==(StringRef const& ref) const;
private:
void set(String s);
Maybe<String> m_held;
char const* m_ptr;
};
std::ostream& operator<<(std::ostream& os, StringRef const& ref);
template<>
struct hash<StringRef> {
size_t operator()(StringRef const& s) const;
CharHasher<char> impl;
};
struct CaseInsensitiveStringHash {
size_t operator()(StringRef const& s) const;
CharHasher<char> impl;
};
struct CaseInsensitiveStringCompare {
bool operator()(StringRef const& lhs, StringRef const& rhs) const;
};
// A sort of version of HashMap<String, T> that is much faster than the normal
// one for a great many use cases. For example, normal HashMap<String, T>
// forces the user to copy from a char* into a String to do basic things like
// search. This class avoids that by nicely supporting char const* as a key
// type without inducing copying.
//
// It is *almost*, but not *completely* compatible with a normal Map type. In
// particular, iteration is difficult because the key type held internally
// *cannot* be String when using std::unordered_map as the base map. As a
// result, iteration is somewhat weird in that an iterator always returns a
// pair of refs, so something like:
//
// for (auto p : map)
// p.second.mutate();
//
// Will affect the copy in the map, and the second value in the pair is always
// mutable whenever non-const iterators are used.
template<typename MappedT, typename HashT = CharHasher<char>, typename ComparatorT = std::equal_to<StringRef>>
class StringMap {
private:
public:
typedef String key_type;
typedef MappedT mapped_type;
typedef pair<key_type, mapped_type> value_type;
typedef HashT Hash;
typedef ComparatorT Comparator;
struct InternalHash {
bool operator()(StringRef const& s) const;
Hash hash;
};
struct InternalComparator {
bool operator()(StringRef const& a, StringRef const& b) const;
Comparator comparator;
};
typedef std::unordered_map<StringRef, mapped_type, InternalHash, InternalComparator> InternalMap;
typedef typename InternalMap::iterator IteratorBase;
typedef typename InternalMap::const_iterator ConstIteratorBase;
template<typename T>
struct ArrowProxy {
T* operator->();
T t;
};
struct iterator : public IteratorBase {
iterator();
iterator(IteratorBase i);
IteratorBase const& base() const;
ArrowProxy<pair<key_type const&, mapped_type&> const> operator->() const;
pair<key_type const&, mapped_type&> const operator*() const;
};
struct const_iterator : public ConstIteratorBase {
const_iterator();
const_iterator(ConstIteratorBase i);
ConstIteratorBase const& base() const;
ArrowProxy<pair<key_type const&, mapped_type const&> const> operator->() const;
pair<key_type const&, mapped_type const&> const operator*() const;
};
template<typename Container>
static StringMap from(Container const& m) {
return StringMap(m.begin(), m.end());
}
StringMap();
StringMap(StringMap const& map);
StringMap(StringMap&& map);
template<typename InputIterator>
StringMap(InputIterator beg, InputIterator end);
StringMap(std::initializer_list<value_type> list);
StringList keys() const;
List<mapped_type> values() const;
List<value_type> pairs() const;
bool contains(StringRef const& k) const;
mapped_type& get(StringRef const& k);
mapped_type const& get(StringRef const& k) const;
mapped_type value(StringRef const& k, mapped_type d = mapped_type()) const;
Maybe<mapped_type const&> maybe(StringRef const& k) const;
Maybe<mapped_type&> maybe(StringRef const& k);
mapped_type& operator[](StringRef k);
StringMap& operator=(StringMap const& map);
StringMap& operator=(StringMap&& map);
pair<iterator, bool> insert(value_type v);
bool insert(StringRef k, mapped_type v);
iterator insert(iterator pos, value_type v);
bool remove(StringRef const& k);
mapped_type take(StringRef const& k);
const_iterator cbegin() const;
const_iterator cend() const;
const_iterator begin() const;
const_iterator end() const;
iterator begin();
iterator end();
size_t size() const;
iterator erase(iterator i);
size_t erase(key_type const& k);
iterator find(StringRef const& k);
const_iterator find(StringRef const& k) const;
void clear();
bool empty() const;
// Appends all values of given map into this map. If overwite is false, then
// skips values that already exist in this map. Returns false if any keys
// previously existed.
template<typename MapType>
bool merge(MapType const& m, bool overwrite = false);
mapped_type& add(StringRef k, mapped_type v);
bool operator==(StringMap const& m) const;
private:
InternalMap m_map;
};
typedef HashSet<String> StringSet;
template<typename MappedT, typename HashT, typename ComparatorT>
std::ostream& operator<<(std::ostream& os, StringMap<MappedT, HashT, ComparatorT> const& ref);
inline size_t hash<StringRef>::operator()(StringRef const& s) const {
return impl(s.ptr());
};
inline size_t CaseInsensitiveStringHash::operator()(StringRef const& s) const {
auto p = s.ptr();
size_t h = 0;
while (*p) {
String::Char c;
p += utf8DecodeChar(p, &c);
h = h * 101 + String::toLower(c);
}
return h;
}
inline bool CaseInsensitiveStringCompare::operator()(StringRef const& a, StringRef const& b) const {
auto lhs = a.ptr();
auto rhs = b.ptr();
while (*lhs && *rhs) {
String::Char lhsc;
lhs += utf8DecodeChar(lhs, &lhsc);
String::Char rhsc;
rhs += utf8DecodeChar(rhs, &rhsc);
if (String::toLower(lhsc) != String::toLower(rhsc))
return false;
}
return !*lhs && !*rhs;
}
template<typename Container>
StringList& StringList::appendAll(Container&& list) {
Base::appendAll(std::forward<Container&&>(list));
return *this;
}
template<typename Container>
StringList& StringList::prependAll(Container&& list) {
Base::prependAll(std::forward<Container&&>(list));
return *this;
}
template<typename Container>
StringList& StringList::insertAll(size_t pos, Container const& l) {
Base::insertAll(pos, std::forward<Container&&>(l));
return *this;
}
inline StringRef::StringRef(StringRef&& sr) {
operator=(move(sr));
}
inline StringRef::StringRef(String const& s)
: m_ptr(s.utf8Ptr()) {}
inline StringRef::StringRef(String&& s) {
set(move(s));
}
inline StringRef::StringRef(std::string const& s)
: m_ptr(s.c_str()) {}
inline StringRef::StringRef(std::string&& s) {
set(move(s));
}
inline StringRef::StringRef(char const* p)
: m_ptr(p) {}
inline StringRef& StringRef::operator=(StringRef&& sr) {
if (sr.m_held) {
set(sr.m_held.take());
} else {
m_held.reset();
m_ptr = sr.m_ptr;
}
return *this;
}
inline char const* StringRef::ptr() const {
return m_ptr;
}
inline void StringRef::hold() {
if (!m_held)
set(String(m_ptr));
}
inline String const& StringRef::held() const {
return *m_held;
}
inline bool StringRef::operator==(StringRef const& ref) const {
if (m_held) {
if (ref.m_held)
return *m_held == *ref.m_held;
}
return strcmp(ptr(), ref.ptr()) == 0;
}
inline void StringRef::set(String s) {
m_held = move(s);
m_ptr = m_held->utf8Ptr();
}
inline std::ostream& operator<<(std::ostream& os, StringRef const& ref) {
os << ref.ptr();
return os;
}
template<typename MappedT, typename HashT, typename ComparatorT>
bool StringMap<MappedT, HashT, ComparatorT>::InternalHash::operator()(StringRef const& s) const {
return hash(s.ptr());
}
template<typename MappedT, typename HashT, typename ComparatorT>
bool StringMap<MappedT, HashT, ComparatorT>::InternalComparator::operator()(StringRef const& a, StringRef const& b) const {
return comparator(a.ptr(), b.ptr());
}
template<typename MappedT, typename HashT, typename ComparatorT>
template<typename T>
T* StringMap<MappedT, HashT, ComparatorT>::ArrowProxy<T>::operator->() {
return &t;
}
template<typename MappedT, typename HashT, typename ComparatorT>
StringMap<MappedT, HashT, ComparatorT>::iterator::iterator() {}
template<typename MappedT, typename HashT, typename ComparatorT>
StringMap<MappedT, HashT, ComparatorT>::iterator::iterator(IteratorBase i)
: IteratorBase(move(i)) {}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::iterator::base() const -> IteratorBase const& {
return *this;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::iterator::operator->() const -> ArrowProxy<pair<key_type const&, mapped_type&> const> {
return {pair<key_type const&, mapped_type&>(base()->first.held(), base()->second)};
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::iterator::operator*() const -> pair<key_type const&, mapped_type&> const {
return {base()->first.held(), base()->second};
}
template<typename MappedT, typename HashT, typename ComparatorT>
StringMap<MappedT, HashT, ComparatorT>::const_iterator::const_iterator() {}
template<typename MappedT, typename HashT, typename ComparatorT>
StringMap<MappedT, HashT, ComparatorT>::const_iterator::const_iterator(ConstIteratorBase i)
: ConstIteratorBase(move(i)) {}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::const_iterator::base() const -> ConstIteratorBase const& {
return *this;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::const_iterator::operator->() const -> ArrowProxy<pair<key_type const&, mapped_type const&> const> {
return {pair<key_type const&, mapped_type const&>(base()->first.held(), base()->second)};
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::const_iterator::operator*() const -> pair<key_type const&, mapped_type const&> const {
return {base()->first.held(), base()->second};
}
template<typename MappedT, typename HashT, typename ComparatorT>
StringMap<MappedT, HashT, ComparatorT>::StringMap() {}
template<typename MappedT, typename HashT, typename ComparatorT>
StringMap<MappedT, HashT, ComparatorT>::StringMap(StringMap const& map) {
for (auto const& p : map)
insert(p);
}
template<typename MappedT, typename HashT, typename ComparatorT>
StringMap<MappedT, HashT, ComparatorT>::StringMap(StringMap&& map) {
m_map = move(map.m_map);
}
template<typename MappedT, typename HashT, typename ComparatorT>
template<typename InputIterator>
StringMap<MappedT, HashT, ComparatorT>::StringMap(InputIterator beg, InputIterator end) {
for (auto i = beg; i != end; ++i)
insert(*i);
}
template<typename MappedT, typename HashT, typename ComparatorT>
StringMap<MappedT, HashT, ComparatorT>::StringMap(std::initializer_list<value_type> list) {
for (auto const& p : list)
insert(p);
}
template<typename MappedT, typename HashT, typename ComparatorT>
StringList StringMap<MappedT, HashT, ComparatorT>::keys() const {
StringList keys;
for (auto const& p : *this)
keys.append(p.first);
return keys;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::values() const -> List<mapped_type> {
List<mapped_type> values;
for (auto const& p : *this)
values.append(p.second);
return values;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::pairs() const -> List<value_type> {
List<value_type> pairs;
for (auto const& p : *this)
pairs.append(p);
return pairs;
}
template<typename MappedT, typename HashT, typename ComparatorT>
bool StringMap<MappedT, HashT, ComparatorT>::contains(StringRef const& k) const {
return m_map.find(k) != m_map.end();
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::get(StringRef const& k) -> mapped_type& {
auto i = m_map.find(k);
if (i == m_map.end())
throw MapException(strf("Key '%s' not found in StringMap::get()", k.ptr()));
return i->second;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::get(StringRef const& k) const -> mapped_type const& {
return const_cast<StringMap*>(this)->get(k);
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::value(StringRef const& k, mapped_type d) const -> mapped_type {
auto i = m_map.find(k);
if (i == m_map.end())
return move(d);
else
return i->second;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::maybe(StringRef const& k) const -> Maybe<mapped_type const&> {
auto i = m_map.find(k);
if (i == m_map.end())
return {};
else
return i->second;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::maybe(StringRef const& k) -> Maybe<mapped_type&> {
auto i = m_map.find(k);
if (i == m_map.end())
return {};
else
return i->second;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::operator[](StringRef k) -> mapped_type& {
auto i = m_map.find(k);
if (i == m_map.end()) {
k.hold();
i = m_map.insert(pair<StringRef, mapped_type>(move(k), mapped_type())).first;
}
return i->second;
}
template<typename MappedT, typename HashT, typename ComparatorT>
StringMap<MappedT, HashT, ComparatorT>& StringMap<MappedT, HashT, ComparatorT>::operator=(StringMap const& map) {
clear();
for (auto const& p : map)
insert(p);
return *this;
}
template<typename MappedT, typename HashT, typename ComparatorT>
StringMap<MappedT, HashT, ComparatorT>& StringMap<MappedT, HashT, ComparatorT>::operator=(StringMap&& map) {
m_map = move(map.m_map);
return *this;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::insert(value_type v) -> std::pair<iterator, bool> {
return m_map.insert(pair<StringRef, mapped_type>(move(v.first), move(v.second)));
}
template<typename MappedT, typename HashT, typename ComparatorT>
bool StringMap<MappedT, HashT, ComparatorT>::insert(StringRef k, mapped_type v) {
k.hold();
return m_map.insert(pair<StringRef, mapped_type>(move(k), move(v))).second;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::insert(iterator pos, value_type v) -> iterator {
return m_map.insert(pos, pair<StringRef, mapped_type>(move(v.first), move(v.second)));
}
template<typename MappedT, typename HashT, typename ComparatorT>
bool StringMap<MappedT, HashT, ComparatorT>::remove(StringRef const& k) {
auto i = m_map.find(k);
if (i != m_map.end()) {
m_map.erase(i);
return true;
} else {
return false;
}
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::take(StringRef const& k) -> mapped_type {
auto i = m_map.find(k);
if (i != m_map.end()) {
MappedT v = std::move(i->second);
m_map.erase(i);
return std::move(v);
} else {
throw MapException(strf("Key '%s' not found in StringMap::take()", k));
}
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::cbegin() const -> const_iterator {
return m_map.cbegin();
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::cend() const -> const_iterator {
return m_map.cend();
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::begin() const -> const_iterator {
return m_map.begin();
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::end() const -> const_iterator {
return m_map.end();
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::begin() -> iterator {
return m_map.begin();
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::end() -> iterator {
return m_map.end();
}
template<typename MappedT, typename HashT, typename ComparatorT>
size_t StringMap<MappedT, HashT, ComparatorT>::size() const {
return m_map.size();
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::erase(iterator i) -> iterator {
return m_map.erase(i);
}
template<typename MappedT, typename HashT, typename ComparatorT>
size_t StringMap<MappedT, HashT, ComparatorT>::erase(key_type const& k) {
if (remove(k))
return 1;
return 0;
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::find(StringRef const& k) -> iterator {
return m_map.find(k);
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::find(StringRef const& k) const -> const_iterator {
return m_map.find(k);
}
template<typename MappedT, typename HashT, typename ComparatorT>
void StringMap<MappedT, HashT, ComparatorT>::clear() {
m_map.clear();
}
template<typename MappedT, typename HashT, typename ComparatorT>
bool StringMap<MappedT, HashT, ComparatorT>::empty() const {
return m_map.empty();
}
template<typename MappedT, typename HashT, typename ComparatorT>
template<typename MapType>
bool StringMap<MappedT, HashT, ComparatorT>::merge(MapType const& m, bool overwrite) {
return mapMerge(*this, m, overwrite);
}
template<typename MappedT, typename HashT, typename ComparatorT>
auto StringMap<MappedT, HashT, ComparatorT>::add(StringRef k, mapped_type v) -> mapped_type& {
k.hold();
auto p = m_map.insert(pair<StringRef, mapped_type>(move(k), move(v)));
if (!p.second)
throw MapException(strf("Entry with key '%s' already present.", outputAny(k)));
else
return p.first->second;
}
template<typename MappedT, typename HashT, typename ComparatorT>
bool StringMap<MappedT, HashT, ComparatorT>::operator==(StringMap const& m) const {
return mapsEqual(*this, m);
}
template<typename MappedT, typename HashT, typename ComparatorT>
std::ostream& operator<<(std::ostream& os, StringMap<MappedT, HashT, ComparatorT> const& ref) {
printMap(os, ref);
return os;
}
}
#endif

View file

@ -0,0 +1,434 @@
#include "StarUniverseController.hpp"
#include "StarRoot.hpp"
#include "StarException.hpp"
#include "StarBuffer.hpp"
#include "StarJsonVariant.hpp"
#include "StarPerformance.hpp"
#include "StarLogging.hpp"
namespace Star {
UniverseController::UniverseController(TcpSocketPtr socket) : Thread("UniverseController") {
m_socket = socket;
m_parent = 0;
m_stop = false;
m_closed = false;
}
void UniverseController::stop() {
MutexLocker lock(m_lock);
m_stop = true;
m_condition.broadcast();
m_socket->close();
}
bool UniverseController::isClosed() {
return m_closed;
}
void UniverseController::bind(UniverseControllerSet* parent) {
m_parent = parent;
}
void UniverseController::receiveResponse(Variant const& response) {
MutexLocker lock(m_lock);
m_responses.append(response);
m_condition.broadcast();
}
void UniverseController::run() {
try {
Buffer buffer(1024);
MutexLocker lock(m_lock);
while (!m_stop && m_socket->isOpen()) {
buffer.clear();
List<int> modeStack;
bool trivial = true;
int mode = 0;
/*
* Mode list
* 0 primitive mode, accepts json or a single line of text which is interpreted as list of strings
* 1 list mode
* 20 object initial mode
* 21 object next mode
* 22 object colon mode
* 3 string mode 31 espaced char in string mode
* 4 value mode
* 5 literal mode
* 6 done, but search for newline
* -1 complete request
* -2 syntax error
*
*/
while (mode >= 0) {
char c;
if (m_socket->read(&c, 1) != 1)
throw StarException("Unexpected result from socket read\n");
if (c == '\r')
continue; // hello dos
buffer.write(&c, 1);
revisit: if ((mode == 0) || (mode == 1) || (mode == 20) || (mode == 4) || (mode == 6)) {
if ((c == ' ') || (c == '\t')) {
// trim whitespace
continue;
}
}
if ((mode == 1) || (mode == 20) || (mode == 4)) {
if (c == '\n') {
// trim newlines
continue;
}
}
switch (mode) {
case 0: { // primitive mode
if (c == '\n') {
mode = -1; //empty line, meh
} else if (c == '[') {
modeStack.push_back(6);
modeStack.push_back(1);
trivial = false;
mode = 4;
} else if (c == '{') {
modeStack.push_back(6);
modeStack.push_back(20);
trivial = false;
mode = 4;
} else if (c == '"') {
modeStack.push_back(6);
mode = 3;
} else {
}
break;
}
case 1: { // list mode
if (c == ',') {
modeStack.push_back(1);
mode = 4;
} else if (c == ']') {
mode = modeStack.takeLast();
} else
mode = -2;
break;
}
case 20: { // object
if (c == '}') {
mode = modeStack.takeLast();
} else {
modeStack.push_back(22);
mode = 4;
goto revisit;
}
break;
}
case 21: { // continue bit of object pairs
if (c == '}') {
mode = modeStack.takeLast();
} else if (c == ',') {
modeStack.push_back(22);
mode = 4;
}
break;
}
case 22: { // colon bit of object pairs
if (c == ':') {
modeStack.push_back(21);
mode = 4;
} else {
mode = -2;
}
break;
}
case 3: { // string mode
if (c == '\\')
mode = 31;
else if (c == '"')
mode = modeStack.takeLast();
break;
}
case 31: { // escaped in string mode
mode = 3;
break;
}
case 4: { // value mode
if (c == '[') {
modeStack.push_back(mode);
modeStack.push_back(1);
trivial = false;
mode = 4;
} else if (c == '{') {
modeStack.push_back(mode);
modeStack.push_back(20);
trivial = false;
mode = 4;
} else if (c == '"') {
modeStack.push_back(mode);
mode = 3;
} else {
mode = 5;
goto revisit;
}
break;
}
case 5: { // literal mode
if ((c == ' ') || (c == '\t') || (c == '\n') || (c == ',') || (c == ']') || (c == '}') || (c == ':')) {
mode = modeStack.takeLast();
goto revisit;
}
break;
}
case 6: {
if (c == '\n') {
mode = -1;
} else {
mode = -2;
}
break;
}
}
}
bool hasCommand = false;
VariantList request;
if (mode == -2) {
while (true) {
char c;
if (m_socket->read(&c, 1) != 1)
throw StarException("Unexpected result from socket read\n");
if (c != '\n')
continue;
}
VariantList failure;
failure.append("syntaxError");
m_responses.push_back(failure);
} else {
String requestStr = String(buffer.ptr(), buffer.dataSize());
if (requestStr.size() == 0) {
VariantList failure;
failure.append("typeHelpForMoreInfo");
m_responses.push_back(failure);
} else if (trivial) {
VariantList parts;
for (auto part : requestStr.splitWhitespace()) {
if (part.size() > 0)
parts.append(part);
}
if (parts.size() == 0) {
VariantList failure;
failure.append("typeHelpForMoreInfo");
m_responses.push_back(failure);
} else {
request = parts;
hasCommand = true;
}
} else {
try {
Variant parseResult = Variant::parse(requestStr);
bool valid = true;
if (valid && (parseResult.type() != Variant::Type::LIST))
valid = false;
if (valid)
request = parseResult.toList();
if (valid && (request.size() < 1))
valid = false;
if (valid && (request[0].type() != Variant::Type::STRING))
valid = false;
if (!valid) {
VariantList failure;
failure.append("parseError");
failure.append("Must be a list with the first element of string type");
m_responses.push_back(failure);
} else {
hasCommand = true;
}
} catch (const std::exception& e) {
VariantList failure;
failure.append("parseError");
failure.append(e.what());
m_responses.push_back(failure);
}
}
}
if (hasCommand) {
m_parent->commandReceived(make_shared<ControllerCommand>(shared_from_this(), request));
}
if (hasCommand)
while ((m_responses.size() == 0) && (!m_stop))
m_condition.wait(m_lock);
for (auto response : m_responses) {
buffer.clear();
outputUtf8Json(response, IODeviceOutputIterator(&buffer), 0, false);
char c = '\n';
buffer.write(&c, 1);
if (m_socket->write(buffer.ptr(), buffer.dataSize()) != buffer.dataSize())
throw StarException("Unexpected result from socket write\n");
m_socket->flush();
}
m_responses.clear();
}
} catch (const NetworkException& e) {
goto finally;
} catch (const std::exception& e) {
Logger::error("UniverseController: Exception caught: %s", e.what());
goto finally;
} catch (...) {
goto finally;
}
finally: {
m_closed = true;
}
m_parent->notify();
}
UniverseControllerSet::UniverseControllerSet(UniverseServer* universeServer): Thread("UniverseControllerSet") {
m_universeServer = universeServer;
m_stop = false;
m_active = true;
}
void UniverseControllerSet::addNewConnection(UniverseControllerPtr controller) {
MutexLocker lock(m_lock);
if (m_stop)
throw StarException("Shutting down");
controller->bind(this);
m_controllers.add(controller);
m_active = true;
m_condition.broadcast();
controller->start();
}
void UniverseControllerSet::stop() {
MutexLocker lock(m_lock);
m_stop = true;
m_active = true;
m_condition.broadcast();
}
void UniverseControllerSet::notify() {
MutexLocker lock(m_lock);
m_condition.broadcast();
}
void UniverseControllerSet::commandTypeHelpForMoreInfo(ControllerCommandPtr command) {
VariantList result;
result.append("todo");
command->respond(result);
}
void UniverseControllerSet::commandCounters(ControllerCommandPtr command) {
VariantList result;
result.append("counters");
VariantMap config;
if (command->request().size() >=2)
config = command->request()[1].toMap();
bool reset = config.value("reset", true).toBool();
auto assets = Root::singleton().assets();
for (auto records : assets->variant("/perf.config:parenting").list())
Performance::setCounterHierarchy(records.getString(1).utf8Ptr(), records.getString(0).utf8Ptr());
result.append(Star::Performance::dumpToVariant(reset));
command->respond(result);
}
void UniverseControllerSet::commandUnknown(ControllerCommandPtr command) {
VariantList result;
result.append("unkownCommand");
result.append(command->command());
command->respond(result);
}
void UniverseControllerSet::run() {
while (!m_stop) {
{
MutexLocker lock(m_lock);
m_active = false;
}
Set<UniverseControllerPtr> closed;
{
MutexLocker lock(m_lock);
for (auto iter = m_controllers.begin(); iter != m_controllers.end(); iter++) {
auto controller = *iter;
if (controller->isClosed())
closed.add(controller);
}
}
for (auto controller : closed) {
m_controllers.remove(controller);
controller->stop();
controller->join();
}
List<ControllerCommandPtr> commands;
{
MutexLocker lock(m_lock);
for (auto command : m_commandQueue)
commands.push_back(command);
m_commandQueue.clear();
}
for (auto command : commands) {
if (command->command() == "typeHelpForMoreInfo")
commandTypeHelpForMoreInfo(command);
else if (command->command() == "counters")
commandCounters(command);
else if (command->command() == "worlds")
m_universeServer->commandReceived(command);
else if (command->command() == "world")
m_universeServer->commandReceived(command);
else
commandUnknown(command);
}
{
MutexLocker lock(m_lock);
if (!m_active)
m_condition.wait(m_lock);
}
}
for (auto iter = m_controllers.begin(); iter != m_controllers.end(); iter++) {
auto controller = *iter;
controller->stop();
controller->join();
}
m_controllers.clear();
}
void UniverseControllerSet::commandReceived(ControllerCommandPtr command) {
MutexLocker lock(m_lock);
m_commandQueue.push_back(command);
m_active = true;
m_condition.broadcast();
}
ControllerCommand::ControllerCommand(weak_ptr<UniverseController> controller, VariantList request) {
m_controller = controller;
m_request = request;
m_command = request[0].toString();
}
void ControllerCommand::popOuterCommand(int innerIndex)
{
m_request = m_request[innerIndex].toList();
m_command = m_request[0].toString();
}
String const& ControllerCommand::command() {
return m_command;
}
VariantList& ControllerCommand::request() {
return m_request;
}
void ControllerCommand::respond(VariantList const& response) {
if (auto controller = m_controller.lock())
controller->receiveResponse(response);
}
}

View file

@ -0,0 +1,88 @@
#ifndef STAR_UNIVERSE_CONTROLLER_HPP
#define STAR_UNIVERSE_CONTROLLER_HPP
#include <memory>
#include "StarConfig.hpp"
#include "StarUniverseServer.hpp"
#include "StarTcp.hpp"
#include "StarThread.hpp"
namespace Star {
using namespace std;
class UniverseControllerSet;
class UniverseController : public Thread, public enable_shared_from_this<UniverseController> {
public:
UniverseController(TcpSocketPtr socket);
void stop();
bool isClosed();
void bind(UniverseControllerSet* parent);
void receiveResponse(Variant const&response);
virtual void run();
private:
TcpSocketPtr m_socket;
UniverseControllerSet* m_parent;
bool m_stop;
bool m_closed;
Mutex m_lock;
ConditionVariable m_condition;
List<Variant> m_responses;
};
typedef shared_ptr<UniverseController> UniverseControllerPtr;
class ControllerCommand;
typedef shared_ptr<ControllerCommand> ControllerCommandPtr;
class UniverseControllerSet : public Thread {
public:
UniverseControllerSet(UniverseServer* universeServer);
void addNewConnection(UniverseControllerPtr controller);
void commandReceived(ControllerCommandPtr command);
virtual void run();
void stop();
void notify();
private:
UniverseServer* m_universeServer;
bool m_stop;
Mutex m_lock;
bool m_active;
ConditionVariable m_condition;
Set<UniverseControllerPtr> m_controllers;
List<ControllerCommandPtr> m_commandQueue;
void commandTypeHelpForMoreInfo(ControllerCommandPtr command);
void commandCounters(ControllerCommandPtr command);
void commandUnknown(ControllerCommandPtr command);
};
typedef shared_ptr<UniverseControllerSet> UniverseControllerSetPtr;
class ControllerCommand {
public:
ControllerCommand(weak_ptr<UniverseController> controller, VariantList request);
String const& command();
VariantList& request();
void respond(VariantList const& response);
void popOuterCommand(int innerIndex);
private:
weak_ptr<UniverseController> m_controller;
String m_command;
VariantList m_request;
};
}
#endif

View file

@ -0,0 +1,816 @@
#include "StarNetStates.hpp"
#include "StarNetStepElements.hpp"
#include "StarDataStreamExtra.hpp"
#include "gtest/gtest.h"
using namespace Star;
TEST(NetSyncStates, Values) {
enum class Test {
Value1,
Value2,
Value3
};
NetStatesEnum<Test> masterField1(Test::Value2);
NetSyncStates master(masterField1);
EXPECT_EQ(masterField1.get(), Test::Value2);
masterField1.set(Test::Value3);
EXPECT_EQ(masterField1.get(), Test::Value3);
}
TEST(NetSyncStates, DataRounding) {
auto masterField1 = NetStatesUInt::makeUInt8();
auto masterField2 = NetStatesUInt::makeUInt16();
auto masterField3 = NetStatesFloat::makeNormalizedFloat8();
auto masterField4 = NetStatesFloat::makeFixedPoint16(0.1);
auto masterField5 = NetStatesFloat::makeFixedPoint16(0.5);
auto masterField6 = NetStatesFloat::makeNormalizedFloat16();
auto masterField7 = NetStatesFloat::makeFixedPoint8(0.1);
NetSyncStates master(masterField1, masterField2, masterField3, masterField4, masterField5, masterField6, masterField7);
masterField1.set(300);
masterField2.set(100000);
masterField3.set(1.5);
masterField4.set(2.1999);
masterField5.set(100000);
masterField6.set(0.777777);
masterField7.set(0.999);
// Check the master side value bounds limits
EXPECT_EQ(masterField1.get(), 255u);
EXPECT_EQ(masterField2.get(), 65535u);
EXPECT_LT(fabs(masterField3.get() - 1.0), 0.00001);
EXPECT_LT(fabs(masterField5.get() - 16383.5), 0.00001);
// Check to make sure encoded data is actually sent with the expected
// limitations to the client side.
auto slaveField1 = NetStatesUInt::makeUInt8();
auto slaveField2 = NetStatesUInt::makeUInt16();
auto slaveField3 = NetStatesFloat::makeNormalizedFloat8();
auto slaveField4 = NetStatesFloat::makeFixedPoint16(0.1);
auto slaveField5 = NetStatesFloat::makeFixedPoint16(0.5);
auto slaveField6 = NetStatesFloat::makeNormalizedFloat16();
auto slaveField7 = NetStatesFloat::makeFixedPoint8(0.1);
NetSyncStates slave(slaveField1, slaveField2, slaveField3, slaveField4, slaveField5, slaveField6, slaveField7);
slave.readDeltaPacket(master.writeDeltaPacket());
EXPECT_EQ(slaveField1.get(), 255u);
EXPECT_EQ(slaveField2.get(), 65535u);
EXPECT_LT(fabs(slaveField3.get() - 1.0), 0.00001);
EXPECT_LT(fabs(slaveField4.get() - 2.2), 0.00001);
EXPECT_LT(fabs(slaveField5.get() - 16383.5), 0.00001);
EXPECT_LT(fabs(slaveField6.get() - 0.777782), 0.000001);
EXPECT_LT(fabs(slaveField7.get() - 1.0), 0.000001);
// Make sure that jittering a fixed point or limited value doesn't cause
// extra deltas
masterField1.set(256);
masterField2.set(65536);
masterField4.set(2.155);
masterField4.set(2.24);
EXPECT_FALSE(master.hasDelta());
}
TEST(NetStepStates, DirectWriteRead) {
auto masterField1 = NetStatesUInt::makeVarUInt(1);
auto masterField2 = NetStatesUInt::makeVarUInt(2);
auto masterField3 = NetStatesUInt::makeVarUInt(3);
auto masterField4 = NetStatesUInt::makeVarUInt(4);
NetStepStates master(masterField1, masterField2, masterField3, masterField4);
auto slaveField1 = NetStatesUInt::makeVarUInt();
auto slaveField2 = NetStatesUInt::makeVarUInt();
auto slaveField3 = NetStatesUInt::makeVarUInt();
auto slaveField4 = NetStatesUInt::makeVarUInt();
NetStepStates slave(slaveField1, slaveField2, slaveField3, slaveField4);
DataStreamBuffer ds;
master.writeFull(ds);
ds.seek(0);
slave.readFull(ds);
EXPECT_EQ(slaveField1.get(), 1u);
EXPECT_EQ(slaveField2.get(), 2u);
EXPECT_EQ(slaveField3.get(), 3u);
EXPECT_EQ(slaveField4.get(), 4u);
master.updateStep(1);
masterField1.set(10);
masterField3.set(30);
ds.clear();
master.writeDelta(ds, 1);
ds.seek(0);
slave.readDelta(ds, 1);
EXPECT_EQ(slaveField1.get(), 10u);
EXPECT_EQ(slaveField2.get(), 2u);
EXPECT_EQ(slaveField3.get(), 30u);
EXPECT_EQ(slaveField4.get(), 4u);
master.updateStep(2);
masterField2.set(20);
masterField4.set(40);
ds.clear();
master.writeDelta(ds, 2);
ds.seek(0);
slave.readDelta(ds, 2);
EXPECT_EQ(slaveField1.get(), 10u);
EXPECT_EQ(slaveField2.get(), 20u);
EXPECT_EQ(slaveField3.get(), 30u);
EXPECT_EQ(slaveField4.get(), 40u);
}
TEST(NetSyncStates, MasterSlave) {
auto masterField1 = NetStatesInt::makeInt8();
auto masterField2 = NetStatesUInt::makeUInt16();
auto masterField3 = NetStatesUInt::makeVarUInt();
auto masterField4 = NetStatesSize();
NetSyncStates master(masterField1, masterField2, masterField3, masterField4);
auto slaveField1 = NetStatesInt::makeInt8();
auto slaveField2 = NetStatesUInt::makeUInt16();
auto slaveField3 = NetStatesUInt::makeVarUInt();
auto slaveField4 = NetStatesSize();
NetSyncStates slave(slaveField1, slaveField2, slaveField3, slaveField4);
masterField1.set(10);
masterField2.set(20);
masterField3.set(30);
masterField4.set(40);
EXPECT_EQ(masterField1.get(), 10);
EXPECT_EQ(masterField2.get(), 20u);
EXPECT_EQ(masterField3.get(), 30u);
EXPECT_EQ(masterField4.get(), 40u);
auto delta = master.writeDeltaPacket();
// Initial delta should be at least 9 bytes, from the 1 2-byte value, 3 >= 1
// byte value and indexes.
EXPECT_GE(delta.size(), 9u);
slave.readDeltaPacket(delta);
EXPECT_EQ(slaveField1.get(), 10);
EXPECT_EQ(slaveField2.get(), 20u);
EXPECT_EQ(slaveField3.get(), 30u);
EXPECT_EQ(slaveField4.get(), 40u);
masterField1.set(50);
delta = master.writeDeltaPacket();
// Second delta should be not include any other data than the single 1 byte
// changed state, so make sure that it is <= 2 bytes.
EXPECT_LE(delta.size(), 2u);
slave.readDeltaPacket(delta);
EXPECT_EQ(slaveField1.get(), 50);
master.reset();
delta = master.writeDeltaPacket();
// Delta after calling resetStep should contain all values.
EXPECT_GE(delta.size(), 9u);
}
TEST(NetSyncStates, CustomData) {
NetStatesData<Vec2F> masterField1;
NetSyncStates master(masterField1);
NetStatesData<Vec2F> slaveField1;
NetSyncStates slave(slaveField1);
masterField1.set(Vec2F(1, 2));
slave.readDeltaPacket(master.writeDeltaPacket());
EXPECT_TRUE(slaveField1.get() == Vec2F(1, 2));
masterField1.update([](Vec2F& v) {
v = {3.0f, 4.0f};
return true;
});
slave.readDeltaPacket(master.writeDeltaPacket());
EXPECT_TRUE(slaveField1.get() == Vec2F(3, 4));
}
TEST(NetSyncStates, Forwarding) {
auto masterField = NetStatesInt::makeInt16();
NetSyncStates master(masterField);
auto forwarderField = NetStatesInt::makeInt16();
NetSyncStates forwarder(forwarderField);
auto slaveField = NetStatesInt::makeInt16();
NetSyncStates slave(slaveField);
masterField.set(413);
forwarder.readDeltaPacket(master.writeDeltaPacket());
slave.readDeltaPacket(forwarder.writeDeltaPacket());
EXPECT_EQ(forwarderField.get(), 413);
EXPECT_EQ(slaveField.get(), 413);
}
TEST(NetStepStates, Forwarding) {
auto masterField1 = NetStatesInt::makeInt16();
auto masterField2 = NetStatesInt::makeInt16();
auto masterField3 = NetStatesInt::makeInt16();
auto masterField4 = NetStatesInt::makeInt16();
NetStepStates master(masterField1, masterField2, masterField3, masterField4);
auto forwarderField1 = NetStatesInt::makeInt16();
auto forwarderField2 = NetStatesInt::makeInt16();
auto forwarderField3 = NetStatesInt::makeInt16();
auto forwarderField4 = NetStatesInt::makeInt16();
NetStepStates forwarder(forwarderField1, forwarderField2, forwarderField3, forwarderField4);
auto slaveField1 = NetStatesInt::makeInt16();
auto slaveField2 = NetStatesInt::makeInt16();
auto slaveField3 = NetStatesInt::makeInt16();
auto slaveField4 = NetStatesInt::makeInt16();
NetStepStates slave(slaveField1, slaveField2, slaveField3, slaveField4);
// First emulate store / load that would happen in entity initialization.
masterField1.set(10);
masterField2.set(20);
masterField3.set(30);
masterField4.set(40);
forwarder.readFullPacket(master.writeFullPacket());
slave.readFullPacket(forwarder.writeFullPacket());
EXPECT_EQ(forwarderField1.get(), 10);
EXPECT_EQ(forwarderField2.get(), 20);
EXPECT_EQ(forwarderField3.get(), 30);
EXPECT_EQ(forwarderField4.get(), 40);
EXPECT_EQ(slaveField1.get(), 10);
EXPECT_EQ(slaveField2.get(), 20);
EXPECT_EQ(slaveField3.get(), 30);
EXPECT_EQ(slaveField4.get(), 40);
// Then, update one step and transmit that step to the forwarder.
master.updateStep(1);
masterField1.set(413);
// Write delta since and including step 1 (the update to "test1")
ByteArray delta = master.writeDeltaPacket(1);
// Make sure that the delta includes only one changed state.
EXPECT_LT(delta.size(), 5u);
forwarder.updateStep(1);
EXPECT_TRUE(forwarder.readDeltaPacket(delta));
EXPECT_EQ(forwarderField1.get(), 413);
// Write forwarded delta since and including step 1.
delta = forwarder.writeDeltaPacket(1);
// Make sure that the delta includes only one changed state.
EXPECT_LT(delta.size(), 5u);
// Make sure that forwarded state makes it all the way to the slave.
slave.updateStep(1);
EXPECT_TRUE(slave.readDeltaPacket(delta));
EXPECT_EQ(slaveField1.get(), 413);
EXPECT_EQ(forwarderField1.get(), 413);
EXPECT_EQ(forwarderField2.get(), 20);
EXPECT_EQ(forwarderField3.get(), 30);
EXPECT_EQ(forwarderField4.get(), 40);
EXPECT_EQ(slaveField1.get(), 413);
EXPECT_EQ(slaveField2.get(), 20);
EXPECT_EQ(slaveField3.get(), 30);
EXPECT_EQ(slaveField4.get(), 40);
}
TEST(NetStepStates, MasterSetGet) {
// Make sure that Master mode sets, gets, pullStateUpdated, and pullEventOccurred
// work properly.
auto masterField1 = NetStatesInt::makeInt16();
auto masterField2 = NetStatesString();
auto masterField3 = NetStatesEvent();
NetStepStates master(masterField1, masterField2, masterField3);
EXPECT_EQ(masterField3.pullOccurrences(), 0u);
masterField1.set(10);
masterField2.set("foo");
EXPECT_EQ(masterField1.pullUpdated(), true);
EXPECT_EQ(masterField2.pullUpdated(), true);
EXPECT_EQ(masterField1.pullUpdated(), false);
EXPECT_EQ(masterField2.pullUpdated(), false);
masterField3.trigger();
masterField3.trigger();
EXPECT_EQ(masterField3.pullOccurrences(), 2u);
EXPECT_EQ(masterField3.pullOccurrences(), 0u);
}
TEST(NetStepStates, IsolatedSetGet) {
auto masterField1 = NetStatesInt::makeInt16();
auto masterField2 = NetStatesString();
auto masterField3 = NetStatesEvent();
EXPECT_EQ(masterField3.pullOccurrences(), 0u);
masterField1.set(10);
masterField2.set("foo");
EXPECT_EQ(masterField1.pullUpdated(), true);
EXPECT_EQ(masterField2.pullUpdated(), true);
EXPECT_EQ(masterField1.pullUpdated(), false);
EXPECT_EQ(masterField2.pullUpdated(), false);
masterField3.trigger();
masterField3.trigger();
EXPECT_EQ(masterField3.pullOccurrences(), 2u);
EXPECT_EQ(masterField3.pullOccurrences(), 0u);
masterField1.set(20);
masterField2.set("bar");
masterField3.trigger();
masterField3.trigger();
NetStepStates master(masterField1, masterField2, masterField3);
EXPECT_EQ(masterField1.get(), 20);
EXPECT_EQ(masterField2.get(), "bar");
EXPECT_EQ(masterField1.pullUpdated(), true);
EXPECT_EQ(masterField2.pullUpdated(), true);
EXPECT_EQ(masterField1.pullUpdated(), false);
EXPECT_EQ(masterField2.pullUpdated(), false);
EXPECT_EQ(masterField3.pullOccurrences(), 2u);
EXPECT_EQ(masterField3.pullOccurrences(), 0u);
}
TEST(NetStepStates, EventTest) {
auto masterField1 = NetStatesInt::makeVarInt();
auto masterField2 = NetStatesEvent();
NetStepStates master(masterField1, masterField2);
auto slaveField1 = NetStatesInt::makeVarInt();
auto slaveField2 = NetStatesEvent();
NetStepStates slave(slaveField1, slaveField2);
// Fields should always start with pullUpdatd returning true, events should
// always start with occurred false.
EXPECT_EQ(slaveField1.pullUpdated(), true);
EXPECT_EQ(slaveField2.pullOccurrences(), 0u);
masterField2.trigger();
EXPECT_TRUE(slave.readDeltaPacket(master.writeDeltaPacket(0)));
EXPECT_EQ(slaveField2.pullOccurrences(), 1u);
EXPECT_EQ(slaveField2.pullOccurrences(), 0u);
EXPECT_FALSE(master.updateStep(1));
// Delta should be empty, nothing happened on step 1.
EXPECT_EQ(master.hasDelta(1), false);
auto delta = master.writeDeltaPacket(1);
EXPECT_TRUE(delta.empty());
EXPECT_FALSE(slave.updateStep(1));
EXPECT_FALSE(slave.readDeltaPacket(delta));
EXPECT_EQ(slaveField2.pullOccurrences(), 0u);
EXPECT_FALSE(master.updateStep(2));
masterField2.trigger();
EXPECT_EQ(master.hasDelta(2), true);
delta = master.writeDeltaPacket(2);
EXPECT_FALSE(slave.updateStep(2));
EXPECT_TRUE(slave.readDeltaPacket(delta));
EXPECT_EQ(slaveField2.pullOccurrences(), 1u);
EXPECT_EQ(slaveField2.pullOccurrences(), 0u);
}
TEST(NetSyncStates, Resetting) {
auto masterField1 = NetStatesString("foo");
auto masterField2 = NetStatesInt::makeVarInt();
auto masterField3 = NetStatesEvent();
NetSyncStates master(masterField1, masterField2, masterField3);
auto slaveField1 = NetStatesString();
auto slaveField2 = NetStatesInt::makeVarInt();
auto slaveField3 = NetStatesEvent();
NetSyncStates slave(slaveField1, slaveField2, slaveField3);
EXPECT_EQ(slaveField1.pullUpdated(), true);
EXPECT_EQ(slaveField2.pullUpdated(), true);
masterField3.trigger();
master.reset();
slave.readDeltaPacket(master.writeDeltaPacket());
EXPECT_EQ(slaveField1.pullUpdated(), true);
EXPECT_EQ(slaveField1.get(), "foo");
EXPECT_EQ(slaveField2.pullUpdated(), false);
// We have triggered an event and called stepReset, the event should still
// come through
EXPECT_EQ(masterField3.pullOccurrences(), 1u);
EXPECT_EQ(slaveField3.pullOccurrences(), 1u);
masterField1.set("foo");
slave.readDeltaPacket(master.writeDeltaPacket());
EXPECT_EQ(slaveField1.pullUpdated(), false);
masterField1.set("bar");
slave.readDeltaPacket(master.writeDeltaPacket());
EXPECT_EQ(slaveField1.pullUpdated(), true);
EXPECT_EQ(slaveField1.get(), "bar");
masterField1.push("bar");
slave.readDeltaPacket(master.writeDeltaPacket());
EXPECT_EQ(slaveField1.pullUpdated(), true);
EXPECT_EQ(slaveField1.get(), "bar");
masterField3.trigger();
masterField3.trigger();
slave.readDeltaPacket(master.writeDeltaPacket());
slaveField3.ignoreOccurrences();
// occurrence should not come through after "ignoreOccurrences"
EXPECT_EQ(slaveField3.pullOccurrences(), 0u);
EXPECT_EQ(masterField3.pullOccurrences(), 2u);
masterField3.trigger();
masterField3.trigger();
masterField3.ignoreOccurrences();
slave.readDeltaPacket(master.writeDeltaPacket());
// ignoreOccurrences is LOCAL only, so events should still go through to the
// slave
EXPECT_EQ(masterField3.pullOccurrences(), 0u);
EXPECT_EQ(slaveField3.pullOccurrences(), 2u);
EXPECT_EQ(slaveField3.pullOccurrences(), 0u);
}
TEST(NetStepStates, Interpolation) {
auto masterField1 = NetStatesFloat::makeFloat(1.0f);
auto masterField2 = NetStatesBool(true);
NetStepStates master(masterField1, masterField2);
auto slaveField1 = NetStatesFloat::makeFloat();
slaveField1.setInterpolator(NetStatesLerpInterpolator);
auto slaveField2 = NetStatesBool();
NetStepStates slave(slaveField1, slaveField2);
slave.enableInterpolation(0);
EXPECT_TRUE(slave.readDeltaPacket(master.writeDeltaPacket(0), 0.0));
EXPECT_FALSE(master.updateStep(2));
masterField1.set(2.0f);
masterField2.set(false);
EXPECT_FALSE(slave.readDeltaPacket(master.writeDeltaPacket(1), 2.0));
EXPECT_FALSE(master.updateStep(4));
masterField1.set(3.0f);
masterField2.set(true);
EXPECT_FALSE(slave.readDeltaPacket(master.writeDeltaPacket(3), 4.0));
EXPECT_FALSE(master.updateStep(6));
masterField1.set(4.0f);
masterField2.set(false);
EXPECT_FALSE(slave.readDeltaPacket(master.writeDeltaPacket(5), 6.0));
EXPECT_TRUE(fabs(slaveField1.get() - 1.0) < 0.001);
EXPECT_EQ(slaveField2.get(), true);
EXPECT_EQ(slaveField2.pullUpdated(), true);
EXPECT_EQ(slaveField2.get(), true);
EXPECT_TRUE(slave.updateStep(1));
EXPECT_TRUE(fabs(slaveField1.get() - 1.5) < 0.001);
EXPECT_EQ(slaveField2.get(), true);
EXPECT_EQ(slaveField2.pullUpdated(), false);
EXPECT_TRUE(slave.updateStep(2));
EXPECT_TRUE(fabs(slaveField1.get() - 2.0) < 0.001);
EXPECT_EQ(slaveField2.get(), false);
EXPECT_EQ(slaveField2.pullUpdated(), true);
EXPECT_TRUE(slave.updateStep(3));
EXPECT_TRUE(fabs(slaveField1.get() - 2.5) < 0.001);
EXPECT_EQ(slaveField2.get(), false);
EXPECT_EQ(slaveField2.pullUpdated(), false);
EXPECT_TRUE(slave.updateStep(4));
EXPECT_TRUE(fabs(slaveField1.get() - 3.0) < 0.001);
EXPECT_EQ(slaveField2.get(), true);
EXPECT_EQ(slaveField2.pullUpdated(), true);
EXPECT_EQ(slaveField2.get(), true);
EXPECT_TRUE(slave.updateStep(5));
EXPECT_TRUE(fabs(slaveField1.get() - 3.5) < 0.001);
EXPECT_EQ(slaveField2.get(), true);
EXPECT_EQ(slaveField2.pullUpdated(), false);
EXPECT_TRUE(slave.updateStep(6));
EXPECT_TRUE(fabs(slaveField1.get() - 4.0) < 0.001);
EXPECT_FALSE(slaveField2.get());
EXPECT_EQ(slaveField2.pullUpdated(), true);
EXPECT_EQ(slaveField2.get(), false);
EXPECT_FALSE(slave.updateStep(7));
EXPECT_FALSE(slave.updateStep(8));
}
TEST(NetStepStates, NetStepElements) {
class TestElement : public NetStepElement {
public:
TestElement(int value = 0)
: dataState(NetStatesInt::makeVarInt(value)), netStates(dataState) {}
void setData(int value) {
dataState.set(value);
}
int getData() const {
return dataState.get();
}
ByteArray netStore() override {
return netStates.writeFullPacket();
}
void netLoad(ByteArray const& store) override {
netStates.readFullPacket(store);
}
void updateNetStep(uint64_t step) override {
netStates.updateStep(step);
}
void resetNetStep() override {
netStates.resetStep();
}
ByteArray writeNetDelta(uint64_t fromStep) override {
return netStates.writeDeltaPacket(fromStep);
}
void readNetDelta(ByteArray const& data, double interpolationStep = 0.0) override {
netStates.readDeltaPacket(data, interpolationStep);
}
void enableNetInterpolation(unsigned extrapolationHint) override {
netStates.enableInterpolation(extrapolationHint);
}
void disableNetInterpolation() override {
netStates.disableInterpolation();
}
void netInterpolationHeartbeat(double step) override {
netStates.interpolationHeartbeat(step);
}
private:
NetStatesInt dataState;
NetStepStates netStates;
};
typedef NetStepDynamicElementGroup<TestElement> TestElementGroup;
TestElementGroup masterGroup;
TestElementGroup slaveGroup;
auto objId1 = masterGroup.addElement(make_shared<TestElement>(1000));
auto objId2 = masterGroup.addElement(make_shared<TestElement>(2000));
EXPECT_EQ(masterGroup.elementIds().size(), 2u);
EXPECT_EQ(masterGroup.getElement(objId1)->getData(), 1000);
EXPECT_EQ(masterGroup.getElement(objId2)->getData(), 2000);
slaveGroup.netLoad(masterGroup.netStore());
EXPECT_EQ(slaveGroup.elementIds().size(), 2u);
EXPECT_EQ(slaveGroup.getElement(objId1)->getData(), 1000);
EXPECT_EQ(slaveGroup.getElement(objId2)->getData(), 2000);
masterGroup.updateNetStep(2);
masterGroup.getElement(objId1)->setData(1001);
masterGroup.getElement(objId2)->setData(2001);
slaveGroup.updateNetStep(2);
slaveGroup.readNetDelta(masterGroup.writeNetDelta(1), 2);
EXPECT_EQ(slaveGroup.getElement(objId1)->getData(), 1001);
EXPECT_EQ(slaveGroup.getElement(objId2)->getData(), 2001);
masterGroup.updateNetStep(4);
masterGroup.getElement(objId1)->setData(1002);
masterGroup.getElement(objId2)->setData(2002);
slaveGroup.updateNetStep(4);
slaveGroup.readNetDelta(masterGroup.writeNetDelta(3), 4);
EXPECT_EQ(masterGroup.getElement(objId1)->getData(), 1002);
EXPECT_EQ(masterGroup.getElement(objId2)->getData(), 2002);
EXPECT_EQ(slaveGroup.getElement(objId1)->getData(), 1002);
EXPECT_EQ(slaveGroup.getElement(objId2)->getData(), 2002);
masterGroup.updateNetStep(6);
auto objId3 = masterGroup.addElement(make_shared<TestElement>(3001));
slaveGroup.updateNetStep(6);
slaveGroup.readNetDelta(masterGroup.writeNetDelta(5));
EXPECT_EQ(slaveGroup.elementIds().size(), 3u);
EXPECT_EQ(slaveGroup.getElement(objId3)->getData(), 3001);
// Delta must be greater than MaxChangeDataSteps
masterGroup.updateNetStep(2006);
auto objId4 = masterGroup.addElement(make_shared<TestElement>(4001));
masterGroup.getElement(objId3)->setData(3002);
masterGroup.getElement(objId4)->setData(4002);
slaveGroup.updateNetStep(2006);
slaveGroup.readNetDelta(masterGroup.writeNetDelta(7));
EXPECT_EQ(slaveGroup.elementIds().size(), 4u);
EXPECT_EQ(slaveGroup.getElement(objId1)->getData(), 1002);
EXPECT_EQ(slaveGroup.getElement(objId2)->getData(), 2002);
EXPECT_EQ(slaveGroup.getElement(objId3)->getData(), 3002);
EXPECT_EQ(slaveGroup.getElement(objId4)->getData(), 4002);
TestElementGroup forwardedSlaveGroup;
forwardedSlaveGroup.readNetDelta(slaveGroup.writeNetDelta(0));
forwardedSlaveGroup.updateNetStep(2006);
EXPECT_EQ(forwardedSlaveGroup.elementIds().size(), 4u);
EXPECT_EQ(forwardedSlaveGroup.getElement(objId1)->getData(), 1002);
EXPECT_EQ(forwardedSlaveGroup.getElement(objId2)->getData(), 2002);
EXPECT_EQ(forwardedSlaveGroup.getElement(objId3)->getData(), 3002);
EXPECT_EQ(forwardedSlaveGroup.getElement(objId4)->getData(), 4002);
masterGroup.updateNetStep(2008);
masterGroup.removeElement(objId1);
masterGroup.removeElement(objId3);
masterGroup.getElement(objId2)->setData(2003);
masterGroup.getElement(objId4)->setData(4003);
slaveGroup.updateNetStep(2008);
slaveGroup.readNetDelta(masterGroup.writeNetDelta(2007));
forwardedSlaveGroup.updateNetStep(2008);
forwardedSlaveGroup.readNetDelta(slaveGroup.writeNetDelta(2007));
EXPECT_EQ(slaveGroup.elementIds().size(), 2u);
EXPECT_EQ(slaveGroup.getElement(objId2)->getData(), 2003);
EXPECT_EQ(slaveGroup.getElement(objId4)->getData(), 4003);
EXPECT_EQ(forwardedSlaveGroup.elementIds().size(), 2u);
EXPECT_EQ(forwardedSlaveGroup.getElement(objId2)->getData(), 2003);
EXPECT_EQ(forwardedSlaveGroup.getElement(objId4)->getData(), 4003);
}
TEST(NetSyncStates, AllTypes) {
auto masterField1 = NetStatesInt::makeInt8(9);
auto masterField2 = NetStatesUInt::makeUInt8(7);
auto masterField3 = NetStatesInt::makeInt16(333);
auto masterField4 = NetStatesUInt::makeUInt16(999);
auto masterField5 = NetStatesInt::makeVarInt(567);
auto masterField6 = NetStatesUInt::makeVarUInt(17000);
auto masterField7 = NetStatesSize(22222);
auto masterField8 = NetStatesFloat::makeFloat(1.55f);
auto masterField9 = NetStatesFloat::makeDouble(1.12345678910111213);
auto masterField10 = NetStatesFloat::makeNormalizedFloat8(0.33333333f);
auto masterField11 = NetStatesFloat::makeNormalizedFloat16(0.66666666f);
auto masterField12 = NetStatesFloat::makeFixedPoint8(0.1, 1.6);
auto masterField13 = NetStatesFloat::makeFixedPoint16(0.01, 1.62);
auto masterField14 = NetStatesFloat::makeFixedPoint(0.01, 2000.62);
auto masterField15 = NetStatesBool(true);
NetSyncStates master(
masterField1, masterField2, masterField3, masterField4, masterField5, masterField6, masterField7,
masterField8, masterField9, masterField10, masterField11, masterField12, masterField13, masterField14,
masterField15);
// Events and generics are tested elsewhere
EXPECT_EQ(masterField1.get(), 9);
EXPECT_EQ(masterField2.get(), 7u);
EXPECT_EQ(masterField3.get(), 333);
EXPECT_EQ(masterField4.get(), 999u);
EXPECT_EQ(masterField5.get(), 567);
EXPECT_EQ(masterField6.get(), 17000u);
EXPECT_EQ(masterField7.get(), 22222u);
EXPECT_FLOAT_EQ(masterField8.get(), 1.55f);
EXPECT_FLOAT_EQ(masterField9.get(), 1.12345678910111213);
EXPECT_FLOAT_EQ(masterField10.get(), 0.33333333f);
EXPECT_FLOAT_EQ(masterField11.get(), 0.66666666f);
EXPECT_FLOAT_EQ(masterField12.get(), 1.6);
EXPECT_FLOAT_EQ(masterField13.get(), 1.62);
EXPECT_FLOAT_EQ(masterField14.get(), 2000.62);
EXPECT_EQ(masterField15.get(), true);
auto slaveField1 = NetStatesInt::makeInt8(9);
auto slaveField2 = NetStatesUInt::makeUInt8(7);
auto slaveField3 = NetStatesInt::makeInt16(333);
auto slaveField4 = NetStatesUInt::makeUInt16(999);
auto slaveField5 = NetStatesInt::makeVarInt(567);
auto slaveField6 = NetStatesUInt::makeVarUInt(17000);
auto slaveField7 = NetStatesSize(22222);
auto slaveField8 = NetStatesFloat::makeFloat(1.55f);
auto slaveField9 = NetStatesFloat::makeDouble(1.12345678910111213);
auto slaveField10 = NetStatesFloat::makeNormalizedFloat8(0.33333333f);
auto slaveField11 = NetStatesFloat::makeNormalizedFloat16(0.66666666f);
auto slaveField12 = NetStatesFloat::makeFixedPoint8(0.1, 1.6);
auto slaveField13 = NetStatesFloat::makeFixedPoint16(0.01, 1.62);
auto slaveField14 = NetStatesFloat::makeFixedPoint(0.01, 2000.62);
auto slaveField15 = NetStatesBool(true);
NetSyncStates slave(
slaveField1, slaveField2, slaveField3, slaveField4, slaveField5, slaveField6, slaveField7,
slaveField8, slaveField9, slaveField10, slaveField11, slaveField12, slaveField13, slaveField14,
slaveField15);
slave.readDeltaPacket(master.writeDeltaPacket());
EXPECT_EQ(slaveField1.get(), 9);
EXPECT_EQ(slaveField2.get(), 7u);
EXPECT_EQ(slaveField3.get(), 333);
EXPECT_EQ(slaveField4.get(), 999u);
EXPECT_EQ(slaveField5.get(), 567);
EXPECT_EQ(slaveField6.get(), 17000u);
EXPECT_EQ(slaveField7.get(), 22222u);
EXPECT_FLOAT_EQ(slaveField8.get(), 1.55f);
EXPECT_FLOAT_EQ(slaveField9.get(), 1.12345678910111213);
EXPECT_NEAR(slaveField10.get(), 0.33333333f, 0.00001f);
EXPECT_NEAR(slaveField11.get(), 0.66666666f, 0.00001f);
EXPECT_FLOAT_EQ(slaveField12.get(), 1.6);
EXPECT_FLOAT_EQ(slaveField13.get(), 1.62);
EXPECT_FLOAT_EQ(slaveField14.get(), 2000.62);
EXPECT_EQ(slaveField15.get(), true);
}
TEST(NetStepStates, FieldConnectedSemantics) {
NetStatesString field("foo");
EXPECT_FALSE(field.connected());
EXPECT_EQ(field.get(), "foo");
NetStepStates netStates;
netStates.addField(field);
EXPECT_TRUE(field.connected());
EXPECT_EQ(field.get(), "foo");
netStates.clearFields();
EXPECT_FALSE(field.connected());
EXPECT_EQ(field.get(), "foo");
}
TEST(NetStepStates, HashMatching) {
auto masterField1 = NetStatesUInt::makeUInt8(5);
auto masterField2 = NetStatesUInt::makeUInt16(10);
auto masterField3 = NetStatesFloat::makeNormalizedFloat8();
auto masterField4 = NetStatesFloat::makeFixedPoint16(0.1);
auto masterField5 = NetStatesFloat::makeFixedPoint16(0.5);
auto masterField6 = NetStatesFloat::makeNormalizedFloat16();
NetStepStates master(masterField1, masterField2, masterField3, masterField4, masterField5, masterField6);
NetStepStates slave;
EXPECT_NE(master.fieldDigest(), slave.fieldDigest());
auto slaveField1 = NetStatesUInt::makeUInt8(5);
auto slaveField2 = NetStatesUInt::makeUInt16(10);
auto slaveField3 = NetStatesFloat::makeNormalizedFloat8();
auto slaveField4 = NetStatesFloat::makeFixedPoint16(0.1);
auto slaveField5 = NetStatesFloat::makeFixedPoint16(0.5);
auto slaveField6 = NetStatesFloat::makeNormalizedFloat16();
slave.addFields(slaveField1, slaveField2, slaveField3, slaveField4, slaveField5, slaveField6);
EXPECT_EQ(master.fieldDigest(), slave.fieldDigest());
auto masterField7 = NetStatesFloat::makeFixedPoint8(0.1);
master.addField(masterField7);
EXPECT_NE(master.fieldDigest(), slave.fieldDigest());
auto slaveField7 = NetStatesFloat::makeFixedPoint8(0.1);
slave.addField(slaveField7);
EXPECT_EQ(master.fieldDigest(), slave.fieldDigest());
slave.readFullPacket(master.writeFullPacket());
EXPECT_EQ(slaveField1.get(), 5u);
EXPECT_EQ(slaveField2.get(), 10u);
slave.addField(NetStatesInt::makeInt8());
EXPECT_THROW(slave.readFullPacket(master.writeFullPacket()), NetStatesException);
}