Yet more C++ stuff

This commit is contained in:
Eric Froemling 2020-10-02 15:21:39 -05:00
parent 8c446dd0d6
commit 18ec4dc264
13 changed files with 8 additions and 8008 deletions

View File

@ -3934,14 +3934,14 @@
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ac/96/c3b9934061393fe09cc90ff24b8d",
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/38/2b/5641b3b40846f74f232771ac0457",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/07/e7/d8f0add439e55e3cce5e5768c80f",
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/fd/10/7681acdbd8feccb27175d6ab8609",
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fd/72/faa94ff6532a95c121fcb5a4f788",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d2/d1/99514fbe084fb0480d75f92ecb2c",
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/97/99/5ba65477f8b846beb98d146a1d2c",
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/dd/5d/f8c5b24579236bef5209d7089044",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/5f/ff/5d34815d90dd4cd36f2a6f587958",
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ba/af/659cd48bd1be9b22ba3006ccb5f9",
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/45/1e/cea9badaf52032adb40e6c3b5e21",
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/5d/63/96c2bbbedc03bd23824d7354b07d",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/44/94/4fa92ae4a1e726fb0b37e626e107",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/66/fd/8bb36157e75f78caa5373d9def18",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/af/2d/7546ed3c987435a743442603c21b"
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/9c/53/c0a2b1c2ee30397db0eb367b688c",
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/8b/a1/c3471ecf846cce50d9220f7214c3",
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/fa/95/87cc2ad7f0e780b02cd9ac633d3d",
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/31/6f/9a29e7100425f8a364208ba40f4f"
}

View File

@ -1,199 +0,0 @@
// Copyright (c) 2011-2020 Eric Froemling
#include "ballistica/game/account.h"
#include "ballistica/app/app_globals.h"
#include "ballistica/game/game.h"
#include "ballistica/generic/utils.h"
#include "ballistica/python/python.h"
namespace ballistica {
auto Account::AccountTypeFromString(const std::string& val) -> AccountType {
if (val == "Game Center") {
return AccountType::kGameCenter;
} else if (val == "Game Circle") {
return AccountType::kGameCircle;
} else if (val == "Google Play") {
return AccountType::kGooglePlay;
} else if (val == "Steam") {
return AccountType::kSteam;
} else if (val == "Oculus") {
return AccountType::kOculus;
} else if (val == "NVIDIA China") {
return AccountType::kNvidiaChina;
} else if (val == "Test") {
return AccountType::kTest;
} else if (val == "Local") {
return AccountType::kDevice;
} else if (val == "Server") {
return AccountType::kServer;
} else {
return AccountType::kInvalid;
}
}
auto Account::AccountTypeToString(AccountType type) -> std::string {
switch (type) {
case AccountType::kGameCenter:
return "Game Center";
case AccountType::kGameCircle:
return "Game Circle";
case AccountType::kGooglePlay:
return "Google Play";
case AccountType::kSteam:
return "Steam";
case AccountType::kOculus:
return "Oculus";
case AccountType::kTest:
return "Test";
case AccountType::kDevice:
return "Local";
case AccountType::kServer:
return "Server";
case AccountType::kNvidiaChina:
return "NVIDIA China";
default:
return "";
}
}
auto Account::AccountTypeToIconString(AccountType type) -> std::string {
switch (type) {
case AccountType::kTest:
return g_game->CharStr(SpecialChar::kTestAccount);
case AccountType::kNvidiaChina:
return g_game->CharStr(SpecialChar::kNvidiaLogo);
case AccountType::kGooglePlay:
return g_game->CharStr(SpecialChar::kGooglePlayGamesLogo);
case AccountType::kSteam:
return g_game->CharStr(SpecialChar::kSteamLogo);
case AccountType::kOculus:
return g_game->CharStr(SpecialChar::kOculusLogo);
case AccountType::kGameCenter:
return g_game->CharStr(SpecialChar::kGameCenterLogo);
case AccountType::kGameCircle:
return g_game->CharStr(SpecialChar::kGameCircleLogo);
case AccountType::kDevice:
case AccountType::kServer:
return g_game->CharStr(SpecialChar::kLocalAccount);
default:
return "";
}
}
Account::Account() = default;
auto Account::GetAccountName() -> std::string {
std::lock_guard<std::mutex> lock(mutex_);
return account_name_;
}
auto Account::GetAccountID() -> std::string {
std::lock_guard<std::mutex> lock(mutex_);
return account_id_;
}
auto Account::GetAccountToken() -> std::string {
std::lock_guard<std::mutex> lock(mutex_);
return account_token_;
}
auto Account::GetAccountExtra() -> std::string {
std::lock_guard<std::mutex> lock(mutex_);
return account_extra_;
}
auto Account::GetAccountExtra2() -> std::string {
std::lock_guard<std::mutex> lock(mutex_);
return account_extra_2_;
}
auto Account::GetAccountState(int* state_num) -> AccountState {
std::lock_guard<std::mutex> lock(mutex_);
if (state_num) {
*state_num = account_state_num_;
}
return account_state_;
}
void Account::SetAccountExtra(const std::string& extra) {
std::lock_guard<std::mutex> lock(mutex_);
account_extra_ = extra;
}
void Account::SetAccountExtra2(const std::string& extra) {
std::lock_guard<std::mutex> lock(mutex_);
account_extra_2_ = extra;
}
void Account::SetAccountToken(const std::string& account_id,
const std::string& token) {
std::lock_guard<std::mutex> lock(mutex_);
// Hmm does this compare logic belong in here?
if (account_id_ == account_id) {
account_token_ = token;
}
}
void Account::SetAccount(AccountType account_type, AccountState account_state,
const std::string& account_name,
const std::string& account_id) {
bool call_account_changed = false;
{
std::lock_guard<std::mutex> lock(mutex_);
// We call out to python so need to be in game thread.
assert(InGameThread());
if (account_state_ != account_state
|| g_app_globals->account_type != account_type
|| account_id_ != account_id || account_name_ != account_name) {
// Special case: if they sent a sign-out for an account type that is.
// currently not signed in, ignore it.
if (account_state == AccountState::kSignedOut
&& (account_type != g_app_globals->account_type)) {
// No-op.
} else {
account_state_ = account_state;
g_app_globals->account_type = account_type;
account_id_ = account_id;
account_name_ = Utils::GetValidUTF8(account_name.c_str(), "gthm");
// If they signed out of an account, account type switches to invalid.
if (account_state == AccountState::kSignedOut) {
g_app_globals->account_type = AccountType::kInvalid;
}
account_state_num_ += 1;
call_account_changed = true;
}
}
}
if (call_account_changed) {
// Inform python layer this has changed.
g_python->AccountChanged();
}
}
void Account::SetProductsPurchased(const std::vector<std::string>& products) {
std::lock_guard<std::mutex> lock(mutex_);
std::map<std::string, bool> purchases_old = product_purchases_;
product_purchases_.clear();
for (auto&& i : products) {
product_purchases_[i] = true;
}
if (product_purchases_ != purchases_old) {
product_purchases_state_++;
}
}
auto Account::GetProductPurchased(const std::string& product) -> bool {
std::lock_guard<std::mutex> lock(mutex_);
auto i = product_purchases_.find(product);
if (i == product_purchases_.end()) {
return false;
} else {
return i->second;
}
}
} // namespace ballistica

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,528 +0,0 @@
// Copyright (c) 2011-2020 Eric Froemling
#include "ballistica/game/host_activity.h"
#include <algorithm>
#include "ballistica/dynamics/material/material.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/game/player.h"
#include "ballistica/game/session/host_session.h"
#include "ballistica/generic/lambda_runnable.h"
#include "ballistica/generic/timer.h"
#include "ballistica/input/device/input_device.h"
#include "ballistica/media/component/collide_model.h"
#include "ballistica/media/component/data.h"
#include "ballistica/media/component/model.h"
#include "ballistica/media/component/texture.h"
#include "ballistica/python/python.h"
#include "ballistica/python/python_context_call.h"
#include "ballistica/python/python_sys.h"
#include "ballistica/scene/node/globals_node.h"
#include "ballistica/scene/node/node_type.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
HostActivity::HostActivity(HostSession* host_session) {
// Store a link to the HostSession and add ourself to it.
host_session_ = host_session;
// Create our game timer - gets called whenever game should step.
step_scene_timer_ =
base_timers_.NewTimer(base_time_, kGameStepMilliseconds, 0, -1,
NewLambdaRunnable([this] { StepScene(); }));
SetGameSpeed(1.0f);
{
ScopedSetContext cp(this); // So scene picks us up as context.
scene_ = Object::New<Scene>(0);
// If there's an output stream, add to it.
if (GameStream* out = host_session->GetGameStream()) {
out->AddScene(scene_.get());
}
}
}
HostActivity::~HostActivity() {
shutting_down_ = true;
// Put the scene in shut-down mode before we start killing stuff.
// (this generates warnings, suppresses messages, etc)
scene_->set_shutting_down(true);
// Clear out all python calls registered in our context.
// (should wipe out refs to our activity and prevent them from running without
// a valid activity context)
for (auto&& i : python_calls_) {
if (i.exists()) {
i->MarkDead();
}
}
// Mark all our media dead to clear it out of our output-stream cleanly
for (auto&& i : textures_) {
if (i.second.exists()) {
i.second->MarkDead();
}
}
for (auto&& i : models_) {
if (i.second.exists()) {
i.second->MarkDead();
}
}
for (auto&& i : sounds_) {
if (i.second.exists()) {
i.second->MarkDead();
}
}
for (auto&& i : collide_models_) {
if (i.second.exists()) {
i.second->MarkDead();
}
}
for (auto&& i : materials_) {
if (i.exists()) {
i->MarkDead();
}
}
// Clear our timers and scene; this should wipe out any remaining refs to our
// python activity, allowing it to die.
base_timers_.Clear();
sim_timers_.Clear();
scene_.Clear();
// Report outstanding calls. There shouldn't be any at this point. Actually it
// turns out there's generally 1; whichever call was responsible for killing
// this activity will still be in progress.. so let's report on 2 or more I
// guess.
#if BA_DEBUG_BUILD
PruneDeadRefs(&python_calls_);
if (python_calls_.size() > 1) {
std::string s = "WARNING: " + std::to_string(python_calls_.size())
+ " live PythonContextCalls at shutdown for "
+ "HostActivity" + " (1 call is expected):";
int count = 1;
for (auto& python_call : python_calls_)
s += "\n " + std::to_string(count++) + ": "
+ (*python_call).GetObjectDescription();
Log(s);
}
#endif // BA_DEBUG_BUILD
}
auto HostActivity::GetGameStream() const -> GameStream* {
if (!host_session_.exists()) return nullptr;
return host_session_->GetGameStream();
}
void HostActivity::StepScene() {
int cycle_count = 1;
if (host_session_->benchmark_type() == BenchmarkType::kCPU) {
cycle_count = 100;
}
for (int cycle = 0; cycle < cycle_count; ++cycle) {
assert(InGameThread());
// Clear our player-positions for this step.
// FIXME: Move this to scene and/or player node.
assert(host_session_.exists());
for (auto&& player : host_session_->players()) {
assert(player.exists());
player->set_have_position(false);
}
// Run our sim-time timers.
sim_timers_.Run(scene()->time());
// Send die-messages/etc to out-of-bounds stuff.
HandleOutOfBoundsNodes();
scene()->Step();
}
}
void HostActivity::RegisterCall(PythonContextCall* call) {
assert(call);
python_calls_.emplace_back(call);
// If we're shutting down, just kill the call immediately.
// (we turn all of our calls to no-ops as we shut down)
if (shutting_down_) {
Log("WARNING: adding call to expired activity; call will not function: "
+ call->GetObjectDescription());
call->MarkDead();
}
}
void HostActivity::start() {
if (_started) {
Log("Error: Start called twice for activity.");
}
_started = true;
}
auto HostActivity::GetAsHostActivity() -> HostActivity* { return this; }
auto HostActivity::NewMaterial(const std::string& name)
-> Object::Ref<Material> {
if (shutting_down_) {
throw Exception("can't create materials during activity shutdown");
}
auto m(Object::New<Material>(name, scene()));
materials_.emplace_back(m);
return m;
}
auto HostActivity::GetTexture(const std::string& name) -> Object::Ref<Texture> {
if (shutting_down_) {
throw Exception("can't load assets during activity shutdown");
}
return Media::GetMedia(&textures_, name, scene());
}
auto HostActivity::GetSound(const std::string& name) -> Object::Ref<Sound> {
if (shutting_down_) {
throw Exception("can't load assets during activity shutdown");
}
return Media::GetMedia(&sounds_, name, scene());
}
auto HostActivity::GetData(const std::string& name) -> Object::Ref<Data> {
if (shutting_down_) {
throw Exception("can't load assets during activity shutdown");
}
return Media::GetMedia(&datas_, name, scene());
}
auto HostActivity::GetModel(const std::string& name) -> Object::Ref<Model> {
if (shutting_down_) {
throw Exception("can't load assets during activity shutdown");
}
return Media::GetMedia(&models_, name, scene());
}
auto HostActivity::GetCollideModel(const std::string& name)
-> Object::Ref<CollideModel> {
if (shutting_down_) {
throw Exception("can't load assets during activity shutdown");
}
return Media::GetMedia(&collide_models_, name, scene());
}
void HostActivity::SetPaused(bool val) {
if (paused_ == val) {
return;
}
paused_ = val;
UpdateStepTimerLength();
}
void HostActivity::SetGameSpeed(float speed) {
if (speed == game_speed_) {
return;
}
assert(speed >= 0.0f);
game_speed_ = speed;
UpdateStepTimerLength();
}
void HostActivity::UpdateStepTimerLength() {
if (game_speed_ == 0.0f || paused_) {
step_scene_timer_->SetLength(-1, true, base_time_);
} else {
step_scene_timer_->SetLength(
std::max(1, static_cast<int>(
round(static_cast<float>(kGameStepMilliseconds)
/ (game_speed_ * g_game->debug_speed_mult())))),
true, base_time_);
}
}
void HostActivity::HandleOutOfBoundsNodes() {
if (scene()->out_of_bounds_nodes().empty()) {
out_of_bounds_in_a_row_ = 0;
return;
}
// Make sure someone's handling our out-of-bounds messages.
out_of_bounds_in_a_row_++;
if (out_of_bounds_in_a_row_ > 100) {
Log("Warning: 100 consecutive out-of-bounds messages sent."
" They are probably not being handled properly");
int j = 0;
for (auto&& i : scene()->out_of_bounds_nodes()) {
j++;
Node* n = i.get();
if (n) {
std::string dstr;
PyObject* delegate = n->GetDelegate();
if (delegate) {
dstr = PythonRef(delegate, PythonRef::kAcquire).Str();
}
Log(" node #" + std::to_string(j) + ": type='" + n->type()->name()
+ "' addr=" + Utils::PtrToString(i.get()) + " name='" + n->label()
+ "' delegate=" + dstr);
}
}
out_of_bounds_in_a_row_ = 0;
}
// Send out-of-bounds messages to newly out-of-bounds nodes.
for (auto&& i : scene()->out_of_bounds_nodes()) {
Node* n = i.get();
if (n) {
n->DispatchOutOfBoundsMessage();
}
}
}
void HostActivity::RegisterPyActivity(PyObject* pyActivityObj) {
assert(pyActivityObj && pyActivityObj != Py_None);
assert(!py_activity_weak_ref_.exists());
// Store a python weak-ref to this activity.
py_activity_weak_ref_.Steal(PyWeakref_NewRef(pyActivityObj, nullptr));
}
auto HostActivity::GetPyActivity() const -> PyObject* {
PyObject* obj = py_activity_weak_ref_.get();
if (!obj) return Py_None;
return PyWeakref_GetObject(obj);
}
auto HostActivity::GetHostSession() -> HostSession* {
return host_session_.get();
}
auto HostActivity::GetMutableScene() -> Scene* {
Scene* sg = scene_.get();
assert(sg);
return sg;
}
void HostActivity::SetIsForeground(bool val) {
// If we're foreground, set our scene as foreground.
Scene* sg = scene();
if (val && sg) {
// Set it locally.
g_game->SetForegroundScene(sg);
// Also push it to clients.
if (GameStream* out = GetGameStream()) {
out->SetForegroundScene(scene_.get());
}
}
}
auto HostActivity::globals_node() const -> GlobalsNode* {
return globals_node_.get();
}
auto HostActivity::NewSimTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int {
if (shutting_down_) {
BA_LOG_PYTHON_TRACE_ONCE(
"WARNING: Creating game timer during host-activity shutdown");
return 123; // Dummy.
}
if (length == 0 && repeat) {
throw Exception("Can't add game-timer with length 0 and repeat on");
}
if (length < 0) {
throw Exception("Timer length cannot be < 0 (got " + std::to_string(length)
+ ")");
}
int offset = 0;
Timer* t = sim_timers_.NewTimer(scene()->time(), length, offset,
repeat ? -1 : 0, runnable);
return t->id();
}
auto HostActivity::NewBaseTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int {
if (shutting_down_) {
BA_LOG_PYTHON_TRACE_ONCE(
"WARNING: Creating session-time timer during host-activity shutdown");
return 123; // dummy...
}
if (length == 0 && repeat) {
throw Exception("Can't add session-time timer with length 0 and repeat on");
}
if (length < 0) {
throw Exception("Timer length cannot be < 0");
}
int offset = 0;
Timer* t = base_timers_.NewTimer(base_time_, length, offset, repeat ? -1 : 0,
runnable);
return t->id();
}
void HostActivity::DeleteSimTimer(int timer_id) {
assert(InGameThread());
if (shutting_down_) return;
sim_timers_.DeleteTimer(timer_id);
}
void HostActivity::DeleteBaseTimer(int timer_id) {
assert(InGameThread());
if (shutting_down_) return;
base_timers_.DeleteTimer(timer_id);
}
auto HostActivity::Update(millisecs_t time_advance) -> millisecs_t {
assert(InGameThread());
// We can be killed at any time, so let's keep an eye out for that.
WeakRef<HostActivity> test_ref(this);
assert(test_ref.exists());
// If we haven't been told to start yet, don't do anything more.
if (!_started) {
return 1000;
}
// Advance base time by the specified amount, stopping at all timers along the
// way.
millisecs_t target_base_time = base_time_ + time_advance;
while (!base_timers_.empty()
&& (base_time_ + base_timers_.GetTimeToNextExpire(base_time_)
<= target_base_time)) {
base_time_ += base_timers_.GetTimeToNextExpire(base_time_);
base_timers_.Run(base_time_);
if (!test_ref.exists()) {
return 1000; // The last timer run might have killed us.
}
}
base_time_ = target_base_time;
// Periodically prune various dead refs.
if (base_time_ > next_prune_time_) {
PruneDeadMapRefs(&textures_);
PruneDeadMapRefs(&sounds_);
PruneDeadMapRefs(&collide_models_);
PruneDeadMapRefs(&models_);
PruneDeadRefs(&materials_);
PruneDeadRefs(&python_calls_);
next_prune_time_ = base_time_ + 5000;
}
// Return the time until the next timer goes off.
return base_timers_.empty() ? 1000
: base_timers_.GetTimeToNextExpire(base_time_);
}
void HostActivity::ScreenSizeChanged() { scene()->ScreenSizeChanged(); }
void HostActivity::LanguageChanged() { scene()->LanguageChanged(); }
void HostActivity::DebugSpeedMultChanged() { UpdateStepTimerLength(); }
void HostActivity::GraphicsQualityChanged(GraphicsQuality q) {
scene()->GraphicsQualityChanged(q);
}
void HostActivity::Draw(FrameDef* frame_def) {
if (!_started) return;
scene()->Draw(frame_def);
}
void HostActivity::DumpFullState(GameStream* out) {
// Add our scene.
if (scene_.exists()) {
scene_->Dump(out);
}
// Before doing any nodes, we need to create all materials.
// (but *not* their components, which may reference the nodes that we haven't
// made yet)
for (auto&& i : materials_) {
if (Material* m = i.get()) {
out->AddMaterial(m);
}
}
// Add our media.
for (auto&& i : textures_) {
if (Texture* t = i.second.get()) {
out->AddTexture(t);
}
}
for (auto&& i : sounds_) {
if (Sound* s = i.second.get()) {
out->AddSound(s);
}
}
for (auto&& i : models_) {
if (Model* s = i.second.get()) {
out->AddModel(s);
}
}
for (auto&& i : collide_models_) {
if (CollideModel* m = i.second.get()) {
out->AddCollideModel(m);
}
}
// Add scene's nodes.
if (scene_.exists()) {
scene_->DumpNodes(out);
}
// Ok, now we can fill out our materials since nodes/etc they reference
// exists.
for (auto&& i : materials_) {
if (Material* m = i.get()) {
m->DumpComponents(out);
}
}
}
auto HostActivity::NewTimer(TimeType timetype, TimerMedium length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int {
// Make sure the runnable passed in is reference-managed already.
// (we may not add an initial reference ourself)
assert(runnable->is_valid_refcounted_object());
// We currently support game and base timers.
switch (timetype) {
case TimeType::kSim:
return NewSimTimer(length, repeat, runnable);
case TimeType::kBase:
return NewBaseTimer(length, repeat, runnable);
default:
// Fall back to default for descriptive error otherwise.
return ContextTarget::NewTimer(timetype, length, repeat, runnable);
}
}
void HostActivity::DeleteTimer(TimeType timetype, int timer_id) {
switch (timetype) {
case TimeType::kSim:
DeleteSimTimer(timer_id);
break;
case TimeType::kBase:
DeleteBaseTimer(timer_id);
break;
default:
// Fall back to default for descriptive error otherwise.
ContextTarget::DeleteTimer(timetype, timer_id);
break;
}
}
auto HostActivity::GetTime(TimeType timetype) -> millisecs_t {
switch (timetype) {
case TimeType::kSim:
return scene()->time();
case TimeType::kBase:
return base_time();
default:
// Fall back to default for descriptive error otherwise.
return ContextTarget::GetTime(timetype);
}
}
} // namespace ballistica

View File

@ -63,7 +63,7 @@ class HostActivity : public ContextTarget {
auto globals_node() const -> GlobalsNode*;
void SetPaused(bool val);
auto paused() const -> bool { return paused_; }
void setAllowKickIdlePlayers(bool val) { allow_kick_idle_players_ = val; }
void set_allow_kick_idle_players(bool val) { allow_kick_idle_players_ = val; }
auto getAllowKickIdlePlayers() const -> bool {
return allow_kick_idle_players_;
}

View File

@ -1,418 +0,0 @@
// Copyright (c) 2011-2020 Eric Froemling
#include "ballistica/game/player.h"
#include <algorithm>
#include "ballistica/game/host_activity.h"
#include "ballistica/game/session/host_session.h"
#include "ballistica/generic/utils.h"
#include "ballistica/input/device/joystick.h"
#include "ballistica/python/class/python_class_session_player.h"
#include "ballistica/python/python.h"
#include "ballistica/python/python_context_call.h"
#include "ballistica/scene/node/node_attribute.h"
#include "ballistica/scene/node/node_type.h"
namespace ballistica {
Player::Player(int id_in, HostSession* host_session)
: id_(id_in), creation_time_(GetRealTime()), host_session_(host_session) {
assert(host_session);
assert(InGameThread());
}
Player::~Player() {
assert(InGameThread());
// If we have an input-device attached to us, detach it.
InputDevice* input_device = input_device_.get();
if (input_device) {
input_device->DetachFromPlayer();
}
// Release our ref to ourself if we have one.
if (py_ref_) {
Py_DECREF(py_ref_);
}
}
auto Player::GetName(bool full, bool icon) const -> std::string {
std::string n = full ? full_name_ : name_;
// Quasi-hacky: if they ask for no icon, strip the first char off our string
// if its in the custom-use-range.
if (!icon) {
std::vector<uint32_t> uni = Utils::UnicodeFromUTF8(n, "3f94f4f");
if (!uni.empty() && uni[0] >= 0xE000 && uni[0] <= 0xF8FF) {
uni.erase(uni.begin());
}
return Utils::UTF8FromUnicode(uni);
} else {
return n;
}
}
auto Player::GetHostActivity() const -> HostActivity* {
return host_activity_.get();
}
void Player::SetHostActivity(HostActivity* a) {
assert(InGameThread());
// Make sure we get pulled out of one activity before being added to another.
if (a && in_activity_) {
std::string old_name =
host_activity_.exists()
? PythonRef(host_activity_->GetPyActivity(), PythonRef::kAcquire)
.Str()
: "<nullptr>";
std::string new_name =
PythonRef(a->GetPyActivity(), PythonRef::kAcquire).Str();
BA_LOG_PYTHON_TRACE_ONCE(
"Player::SetHostActivity() called when already in an activity (old="
+ old_name + ", new=" + new_name + ")");
} else if (!a && !in_activity_) {
BA_LOG_PYTHON_TRACE_ONCE(
"Player::SetHostActivity() called with nullptr when not in an "
"activity");
}
host_activity_ = a;
in_activity_ = (a != nullptr);
}
void Player::SetPosition(const Vector3f& position) {
position_ = position;
have_position_ = true;
}
void Player::ResetInput() {
// Hold a ref to ourself while clearing this to make sure
// we don't die midway as a result of freeing something.
Object::Ref<Object> ref(this);
calls_.clear();
left_held_ = right_held_ = up_held_ = down_held_ = have_position_ = false;
}
void Player::SetPyTeam(PyObject* team) {
if (team != nullptr && team != Py_None) {
// We store a weak-ref to this.
py_team_weak_ref_.Steal(PyWeakref_NewRef(team, nullptr));
} else {
py_team_weak_ref_.Release();
}
}
auto Player::GetPyTeam() -> PyObject* {
PyObject* obj = py_team_weak_ref_.get();
if (!obj) {
return Py_None;
}
return PyWeakref_GetObject(obj);
}
void Player::SetPyCharacter(PyObject* character) {
if (character != nullptr && character != Py_None) {
py_character_.Acquire(character);
} else {
py_character_.Release();
}
}
auto Player::GetPyCharacter() -> PyObject* {
return py_character_.exists() ? py_character_.get() : Py_None;
}
void Player::SetPyColor(PyObject* c) { py_color_.Acquire(c); }
auto Player::GetPyColor() -> PyObject* {
return py_color_.exists() ? py_color_.get() : Py_None;
}
void Player::SetPyHighlight(PyObject* c) { py_highlight_.Acquire(c); }
auto Player::GetPyHighlight() -> PyObject* {
return py_highlight_.exists() ? py_highlight_.get() : Py_None;
}
void Player::SetPyActivityPlayer(PyObject* c) { py_activityplayer_.Acquire(c); }
auto Player::GetPyActivityPlayer() -> PyObject* {
return py_activityplayer_.exists() ? py_activityplayer_.get() : Py_None;
}
auto Player::GetPyRef(bool new_ref) -> PyObject* {
assert(InGameThread());
if (py_ref_ == nullptr) {
py_ref_ = PythonClassSessionPlayer::Create(this);
}
if (new_ref) {
Py_INCREF(py_ref_);
}
return py_ref_;
}
void Player::AssignInputCall(InputType type, PyObject* call_obj) {
assert(InGameThread());
assert(static_cast<int>(type) >= 0
&& static_cast<int>(type) < static_cast<int>(InputType::kLast));
// Special case: if they're assigning hold-position-press or
// hold-position-release, or any direction events, we add in a hold-position
// press/release event before we deliver any other events.. that way newly
// created stuff is informed of the hold state and doesn't wrongly think they
// should start moving.
switch (type) {
case InputType::kHoldPositionPress:
case InputType::kHoldPositionRelease:
case InputType::kLeftPress:
case InputType::kLeftRelease:
case InputType::kRightPress:
case InputType::kUpPress:
case InputType::kUpRelease:
case InputType::kDownPress:
case InputType::kDownRelease:
case InputType::kUpDown:
case InputType::kLeftRight: {
send_hold_state_ = true;
break;
}
default:
break;
}
if (call_obj) {
calls_[static_cast<int>(type)] = Object::New<PythonContextCall>(call_obj);
} else {
calls_[static_cast<int>(type)].Clear();
}
// If they assigned l/r, immediately send an update for its current value.
if (type == InputType::kLeftRight) {
RunInput(type, lr_state_);
}
// Same for up/down.
if (type == InputType::kUpDown) {
RunInput(type, ud_state_);
}
// Same for run.
if (type == InputType::kRun) {
RunInput(type, run_state_);
}
// Same for fly.
if (type == InputType::kFlyPress && fly_held_) {
RunInput(type);
}
}
void Player::RunInput(InputType type, float value) {
assert(InGameThread());
const float threshold = kJoystickDiscreteThresholdFloat;
// Most input commands cause us to reset the player's time-out
// there are a few exceptions though - very small analog values
// get ignored since they can come through without user intervention.
bool reset_time_out = true;
if (type == InputType::kLeftRight || type == InputType::kUpDown) {
if (std::abs(value) < 0.3f) {
reset_time_out = false;
}
}
if (type == InputType::kRun) {
if (value < 0.3f) {
reset_time_out = false;
}
}
// Also ignore hold-position stuff since it can come through without user
// interaction.
if ((type == InputType::kHoldPositionPress)
|| (type == InputType::kHoldPositionRelease))
reset_time_out = false;
if (reset_time_out) {
time_out_ = BA_PLAYER_TIME_OUT;
}
// Keep track of the hold-position state that comes through here.
// any-time hold position buttons are re-assigned, we subsequently
// re-send the current hold-state so whatever its driving starts out correctly
// held if need be.
if (type == InputType::kHoldPositionPress) {
hold_position_ = true;
} else if (type == InputType::kHoldPositionRelease) {
hold_position_ = false;
} else if (type == InputType::kFlyPress) {
fly_held_ = true;
} else if (type == InputType::kFlyRelease) {
fly_held_ = false;
}
// If we were supposed to deliver hold-state, go ahead and do that first.
if (send_hold_state_) {
send_hold_state_ = false;
if (hold_position_) {
RunInput(InputType::kHoldPositionPress);
} else {
RunInput(InputType::kHoldPositionRelease);
}
}
// Let's make our life simpler by converting held-position-joystick-events..
{
// We need to store these since we might look at them during a hold-position
// event when we don't have their originating events available.
if (type == InputType::kLeftRight) {
lr_state_ = value;
}
if (type == InputType::kUpDown) {
ud_state_ = value;
}
if (type == InputType::kRun) {
run_state_ = value;
}
// Special input commands - keep track of left/right and up/down positions
// so we can deliver simple "leftUp", "leftDown", etc type of events
// in addition to the standard absolute leftRight positions, etc.
if (type == InputType::kLeftRight || type == InputType::kHoldPositionPress
|| type == InputType::kHoldPositionRelease) {
float arg = lr_state_;
if (hold_position_) {
arg = 0.0f; // Throttle is off.
}
if (left_held_) {
if (arg > -threshold) {
left_held_ = false;
RunInput(InputType::kLeftRelease);
}
} else if (right_held_) {
if (arg < threshold) {
right_held_ = false;
RunInput(InputType::kRightRelease);
}
} else {
if (arg >= threshold) {
if (!left_held_ && !up_held_ && !down_held_) {
right_held_ = true;
RunInput(InputType::kRightPress);
}
} else if (arg <= -threshold) {
if (!right_held_ && !up_held_ && !down_held_) {
left_held_ = true;
RunInput(InputType::kLeftPress);
}
}
}
}
if (type == InputType::kUpDown || type == InputType::kHoldPositionPress
|| type == InputType::kHoldPositionRelease) {
float arg = ud_state_;
if (hold_position_) arg = 0.0f; // throttle is off;
if (up_held_) {
if (arg < threshold) {
up_held_ = false;
RunInput(InputType::kUpRelease);
}
} else if (down_held_) {
if (arg > -threshold) {
down_held_ = false;
RunInput(InputType::kDownRelease);
}
} else {
if (arg <= -threshold) {
if (!left_held_ && !right_held_ && !up_held_) {
down_held_ = true;
RunInput(InputType::kDownPress);
}
} else if (arg >= threshold) {
if (!left_held_ && !up_held_ && !right_held_) {
up_held_ = true;
RunInput(InputType::kUpPress);
}
}
}
}
}
auto j = calls_.find(static_cast<int>(type));
if (j != calls_.end() && j->second.exists()) {
if (type == InputType::kRun) {
PythonRef args(
Py_BuildValue("(f)", std::min(1.0f, std::max(0.0f, value))),
PythonRef::kSteal);
j->second->Run(args.get());
} else if (type == InputType::kLeftRight || type == InputType::kUpDown) {
PythonRef args(
Py_BuildValue("(f)", std::min(1.0f, std::max(-1.0f, value))),
PythonRef::kSteal);
j->second->Run(args.get());
} else {
j->second->Run();
}
}
}
auto Player::GetHostSession() const -> HostSession* {
return host_session_.get();
}
void Player::SetName(const std::string& name, const std::string& full_name,
bool is_real) {
assert(InGameThread());
HostSession* host_session = GetHostSession();
BA_PRECONDITION(host_session);
name_is_real_ = is_real;
name_ = host_session->GetUnusedPlayerName(this, name);
full_name_ = full_name;
// If we're already in the game and our name is changing, we need to update
// the roster.
if (accepted_) {
g_game->UpdateGameRoster();
}
}
void Player::InputCommand(InputType type, float value) {
assert(InGameThread());
switch (type) {
case InputType::kUpDown:
case InputType::kLeftRight:
case InputType::kRun:
RunInput(type, value);
break;
// case InputType::kReset:
// Log("Error: FIXME: player-input-reset command unimplemented");
// break;
default:
RunInput(type);
break;
}
}
void Player::SetInputDevice(InputDevice* input_device) {
input_device_ = input_device;
}
auto Player::GetPublicAccountID() const -> std::string {
assert(InGameThread());
if (input_device_.exists()) {
return input_device_->GetPublicAccountID();
}
return "";
}
void Player::SetIcon(const std::string& tex_name,
const std::string& tint_tex_name,
const std::vector<float>& tint_color,
const std::vector<float>& tint2_color) {
assert(tint_color.size() == 3);
assert(tint2_color.size() == 3);
icon_tex_name_ = tex_name;
icon_tint_tex_name_ = tint_tex_name;
icon_tint_color_ = tint_color;
icon_tint2_color_ = tint2_color;
icon_set_ = true;
}
} // namespace ballistica

View File

@ -1,109 +0,0 @@
// Copyright (c) 2011-2020 Eric Froemling
#include "ballistica/game/player_spec.h"
#include <cstdlib>
#include "ballistica/app/app_globals.h"
#include "ballistica/game/account.h"
#include "ballistica/generic/json.h"
#include "ballistica/generic/utils.h"
#include "ballistica/platform/platform.h"
namespace ballistica {
PlayerSpec::PlayerSpec() : account_type_(AccountType::kInvalid) {}
PlayerSpec::PlayerSpec(const std::string& s) {
cJSON* root_obj = cJSON_Parse(s.c_str());
bool success = false;
if (root_obj) {
cJSON* name_obj = cJSON_GetObjectItem(root_obj, "n");
cJSON* short_name_obj = cJSON_GetObjectItem(root_obj, "sn");
cJSON* account_obj = cJSON_GetObjectItem(root_obj, "a");
if (name_obj && short_name_obj && account_obj) {
name_ = Utils::GetValidUTF8(name_obj->valuestring, "psps");
short_name_ = Utils::GetValidUTF8(short_name_obj->valuestring, "psps2");
// Account type may technically be something we don't recognize,
// but that's ok.. it'll just be 'invalid' to us in that case
account_type_ = Account::AccountTypeFromString(account_obj->valuestring);
success = true;
}
cJSON_Delete(root_obj);
}
if (!success) {
Log("Error creating PlayerSpec from string: '" + s + "'");
name_ = "<error>";
short_name_ = "";
account_type_ = AccountType::kInvalid;
}
}
auto PlayerSpec::GetDisplayString() const -> std::string {
return Account::AccountTypeToIconString(account_type_) + name_;
}
auto PlayerSpec::GetShortName() const -> std::string {
if (short_name_.empty()) {
return name_;
}
return short_name_;
}
auto PlayerSpec::operator==(const PlayerSpec& spec) const -> bool {
// NOTE: need to add account ID in here once that's available
return (spec.name_ == name_ && spec.short_name_ == short_name_
&& spec.account_type_ == account_type_);
}
auto PlayerSpec::GetSpecString() const -> std::string {
cJSON* root;
root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "n", name_.c_str());
cJSON_AddStringToObject(root, "a",
Account::AccountTypeToString(account_type_).c_str());
cJSON_AddStringToObject(root, "sn", short_name_.c_str());
char* out = cJSON_PrintUnformatted(root);
std::string out_s = out;
free(out);
cJSON_Delete(root);
// We should never allow ourself to have all this add up to more than 256.
assert(out_s.size() < 256);
return out_s;
}
auto PlayerSpec::GetAccountPlayerSpec() -> PlayerSpec {
PlayerSpec spec;
if (g_account->GetAccountState() == AccountState::kSignedIn) {
spec.account_type_ = g_app_globals->account_type;
spec.name_ =
Utils::GetValidUTF8(g_account->GetAccountName().c_str(), "bsgaps");
} else {
spec.name_ =
Utils::GetValidUTF8(g_platform->GetDeviceName().c_str(), "bsgaps2");
}
if (spec.name_.size() > 100) {
// FIXME should perhaps clamp this in unicode space
Log("account name size too long: '" + spec.name_ + "'");
spec.name_.resize(100);
spec.name_ = Utils::GetValidUTF8(spec.name_.c_str(), "bsgaps3");
}
return spec;
}
auto PlayerSpec::GetDummyPlayerSpec(const std::string& name) -> PlayerSpec {
PlayerSpec spec;
spec.name_ = Utils::GetValidUTF8(name.c_str(), "bsgdps1");
if (spec.name_.size() > 100) {
// FIXME should perhaps clamp this in unicode space
Log("dummy player spec name too long: '" + spec.name_ + "'");
spec.name_.resize(100);
spec.name_ = Utils::GetValidUTF8(spec.name_.c_str(), "bsgdps2");
}
return spec;
}
} // namespace ballistica

File diff suppressed because it is too large Load Diff

View File

@ -1,765 +0,0 @@
// Copyright (c) 2011-2020 Eric Froemling
#include "ballistica/game/session/host_session.h"
#include "ballistica/core/context.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/game/host_activity.h"
#include "ballistica/game/player.h"
#include "ballistica/generic/lambda_runnable.h"
#include "ballistica/generic/timer.h"
#include "ballistica/graphics/graphics.h"
#include "ballistica/input/device/input_device.h"
#include "ballistica/media/component/data.h"
#include "ballistica/media/component/model.h"
#include "ballistica/media/component/sound.h"
#include "ballistica/media/component/texture.h"
#include "ballistica/python/python.h"
#include "ballistica/python/python_command.h"
#include "ballistica/python/python_context_call.h"
#include "ballistica/python/python_sys.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
HostSession::HostSession(PyObject* session_type_obj)
: last_kick_idle_players_decrement_time_(GetRealTime()) {
assert(g_game);
assert(InGameThread());
assert(session_type_obj != nullptr);
ScopedSetContext cp(this);
// FIXME: Should be an attr of the session class, not hard-coded.
is_main_menu_ =
static_cast<bool>(strstr(Python::ObjToString(session_type_obj).c_str(),
"bastd.mainmenu.MainMenuSession"));
// Log("MAIN MENU? " + std::to_string(is_main_menu()));
kick_idle_players_ = g_game->kick_idle_players();
// Create a timer to step our session scene.
step_scene_timer_ =
base_timers_.NewTimer(base_time_, kGameStepMilliseconds, 0, -1,
NewLambdaRunnable([this] { StepScene(); }));
// Set up our output-stream, which will go to a replay and/or the network.
// We don't dump to a replay if we're doing the main menu; that replay
// would be boring.
bool do_replay = !is_main_menu_;
// Log("DO REPLAY? " + std::to_string(do_replay));
// At the moment headless-server don't write replays.
#if BA_HEADLESS_BUILD
do_replay = false;
#endif // BA_HEADLESS_BUILD
output_stream_ = Object::New<GameStream>(this, do_replay);
// Make a scene for our session-level nodes, etc.
scene_ = Object::New<Scene>(0);
if (output_stream_.exists()) {
output_stream_->AddScene(scene_.get());
}
// Fade in from our current blackness.
g_graphics->FadeScreen(true, 250, nullptr);
// Start by showing the progress bar instead of hitching.
g_graphics->EnableProgressBar(true);
// Now's a good time to run garbage collection; there should be pretty much
// no game stuff to speak of in existence (provided the last session went
// down peacefully).
g_python->obj(Python::ObjID::kGarbageCollectCall).Call();
// Instantiate our python Session instance.
PythonRef obj;
PythonRef session_type(session_type_obj, PythonRef::kAcquire);
{
Python::ScopedCallLabel label("Session instantiation");
obj = session_type.Call();
}
if (!obj.exists()) {
throw Exception("Error creating game session: '" + session_type.Str()
+ "'");
}
// The session python object should have called
// _ba.register_session() in its constructor to set session_py_obj_.
if (session_py_obj_ != obj) {
throw Exception("session not set up correctly");
}
// Lastly, keep the python layer fed with our latest player count in case
// it is updating the master-server with our current/max player counts.
g_game->SetPublicPartyPlayerCount(static_cast<int>(players_.size()));
}
auto HostSession::GetHostSession() -> HostSession* { return this; }
void HostSession::DestroyHostActivity(HostActivity* a) {
BA_PRECONDITION(a);
BA_PRECONDITION(a->GetHostSession() == this);
if (a == foreground_host_activity_.get()) {
foreground_host_activity_.Clear();
}
// Clear it from our activities list if its still on there.
for (auto i = host_activities_.begin(); i < host_activities_.end(); i++) {
if (i->get() == a) {
host_activities_.erase(i);
return;
}
}
// The only reason it wouldn't be there should be because the activity is
// dying due our clearing of the list in our destructor; make sure that's
// the case.
assert(shutting_down_);
}
auto HostSession::GetMutableScene() -> Scene* {
assert(scene_.exists());
return scene_.get();
}
void HostSession::DebugSpeedMultChanged() {
// FIXME - should we progress our own scene faster/slower depending on
// this too? Is there really a need to?
// Let all our activities know.
for (auto&& i : host_activities_) {
i->DebugSpeedMultChanged();
}
}
void HostSession::ScreenSizeChanged() {
// Let our internal scene know.
scene()->ScreenSizeChanged();
// Also let all our activities know.
for (auto&& i : host_activities_) {
i->ScreenSizeChanged();
}
}
void HostSession::LanguageChanged() {
// Let our internal scene know.
scene()->LanguageChanged();
// Also let all our activities know.
for (auto&& i : host_activities_) {
i->LanguageChanged();
}
}
void HostSession::GraphicsQualityChanged(GraphicsQuality q) {
// Let our internal scene know.
scene()->GraphicsQualityChanged(q);
// Let all our activities know.
for (auto&& i : host_activities_) {
i->GraphicsQualityChanged(q);
}
}
auto HostSession::DoesFillScreen() const -> bool {
// FIXME not necessarily the case.
return true;
}
void HostSession::Draw(FrameDef* f) {
// First draw our session scene.
scene()->Draw(f);
// Let all our activities draw their own scenes/etc.
for (auto&& i : host_activities_) {
i->Draw(f);
}
}
auto HostSession::NewTimer(TimerMedium length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int {
if (shutting_down_) {
BA_LOG_PYTHON_TRACE_ONCE(
"WARNING: Creating game timer during host-session shutdown");
return 123; // dummy...
}
if (length == 0 && repeat) {
throw Exception("Can't add game-timer with length 0 and repeat on");
}
if (length < 0) {
throw Exception("Timer length cannot be < 0 (got " + std::to_string(length)
+ ")");
}
int offset = 0;
Timer* t = sim_timers_.NewTimer(scene()->time(), length, offset,
repeat ? -1 : 0, runnable);
return t->id();
}
void HostSession::DeleteTimer(int timer_id) {
assert(InGameThread());
if (shutting_down_) return;
sim_timers_.DeleteTimer(timer_id);
}
auto HostSession::GetSound(const std::string& name) -> Object::Ref<Sound> {
if (shutting_down_) {
throw Exception("can't load assets during session shutdown");
}
return Media::GetMedia(&sounds_, name, scene());
}
auto HostSession::GetData(const std::string& name) -> Object::Ref<Data> {
if (shutting_down_) {
throw Exception("can't load assets during session shutdown");
}
return Media::GetMedia(&datas_, name, scene());
}
auto HostSession::GetTexture(const std::string& name) -> Object::Ref<Texture> {
if (shutting_down_) {
throw Exception("can't load assets during session shutdown");
}
return Media::GetMedia(&textures_, name, scene());
}
auto HostSession::GetModel(const std::string& name) -> Object::Ref<Model> {
if (shutting_down_) {
throw Exception("can't load media during session shutdown");
}
return Media::GetMedia(&models_, name, scene());
}
auto HostSession::GetForegroundContext() -> Context {
HostActivity* a = foreground_host_activity_.get();
if (a) {
return Context(a);
}
return Context(this);
}
void HostSession::RequestPlayer(InputDevice* device) {
assert(InGameThread());
// Ignore if we have no python session obj.
if (!GetSessionPyObj()) {
Log("Error: HostSession::RequestPlayer() called w/no session_py_obj_.");
return;
}
// Need to at least temporarily create and attach to a player for passing to
// the callback.
int player_id = next_player_id_++;
auto player(Object::New<Player>(player_id, this));
players_.push_back(player);
device->AttachToLocalPlayer(player.get());
// Ask the python layer to accept/deny this guy.
bool accept;
{
// Set the session as context.
ScopedSetContext cp(this);
accept = static_cast<bool>(
session_py_obj_.GetAttr("_request_player")
.Call(PythonRef(Py_BuildValue("(O)", player->BorrowPyRef()),
PythonRef::kSteal))
.ValueAsInt());
if (accept) {
player->set_accepted(true);
} else {
RemovePlayer(player.get());
}
}
// If he was accepted, update our game roster with the new info.
if (accept) {
g_game->UpdateGameRoster();
}
// Lastly, keep the python layer fed with our latest player count in case it
// is updating the master-server with our current/max player counts.
g_game->SetPublicPartyPlayerCount(static_cast<int>(players_.size()));
}
void HostSession::RemovePlayer(Player* player) {
assert(player);
for (auto i = players_.begin(); i != players_.end(); ++i) {
if (i->get() == player) {
// Grab a ref to keep the player alive, pull him off the list, then call
// his leaving callback.
Object::Ref<Player> player2 = *i;
players_.erase(i);
// Only make the callback for this player if they were accepted.
if (player2->accepted()) {
IssuePlayerLeft(player2.get());
}
// Update our game roster with the departure.
g_game->UpdateGameRoster();
// Lastly, keep the python layer fed with our latest player count in case
// it is updating the master-server with our current/max player counts.
g_game->SetPublicPartyPlayerCount(static_cast<int>(players_.size()));
return;
}
}
BA_LOG_ERROR_TRACE("Player not found in HostSession::RemovePlayer()");
}
void HostSession::IssuePlayerLeft(Player* player) {
assert(player);
assert(InGameThread());
try {
if (GetSessionPyObj()) {
if (player) {
// Make sure we're the context for session callbacks.
ScopedSetContext cp(this);
Python::ScopedCallLabel label("Session on_player_leave");
session_py_obj_.GetAttr("on_player_leave")
.Call(PythonRef(Py_BuildValue("(O)", player->BorrowPyRef()),
PythonRef::kSteal));
} else {
BA_LOG_PYTHON_TRACE_ONCE("missing player on IssuePlayerLeft");
}
} else {
Log("WARNING: HostSession: IssuePlayerLeft caled with no "
"session_py_obj_");
}
} catch (const std::exception& e) {
Log(std::string("Error calling on_player_leave(): ") + e.what());
}
}
void HostSession::SetKickIdlePlayers(bool enable) {
// If this has changed, reset our disconnect-time reporting.
assert(InGameThread());
if (enable != kick_idle_players_) {
last_kick_idle_players_decrement_time_ = GetRealTime();
}
kick_idle_players_ = enable;
}
void HostSession::SetForegroundHostActivity(HostActivity* a) {
assert(a);
assert(InGameThread());
if (shutting_down_) {
Log("WARNING: SetForegroundHostActivity called during session shutdown; "
"ignoring.");
return;
}
// Sanity check: make sure the one provided is part of this session.
bool found = false;
for (auto&& i : host_activities_) {
if (i == a) {
found = true;
break;
}
}
if ((a->GetHostSession() != this) || !found) {
throw Exception("HostActivity is not part of this HostSession");
}
foreground_host_activity_ = a;
// Now go through telling each host-activity whether it's foregrounded or not.
// FIXME: Dying sessions never get told they're un-foregrounded.. could that
// ever be a problem?
bool session_is_foreground = (g_game->GetForegroundSession() != nullptr);
for (auto&& i : host_activities_) {
i->SetIsForeground(session_is_foreground && (i == a));
}
}
void HostSession::AddHostActivity(HostActivity* a) {
host_activities_.emplace_back(a);
}
// Called by the constructor of the session python object.
void HostSession::RegisterPySession(PyObject* obj) {
session_py_obj_.Acquire(obj);
}
// Given an activity python type, instantiates and returns a new activity.
auto HostSession::NewHostActivity(PyObject* activity_type_obj,
PyObject* settings_obj) -> PyObject* {
PythonRef activity_type(activity_type_obj, PythonRef::kAcquire);
if (!activity_type.CallableCheck()) {
throw Exception("Invalid HostActivity type passed; not callable");
}
// First generate our C++ activity instance and point the context at it.
auto activity(Object::New<HostActivity>(this));
AddHostActivity(activity.get());
ScopedSetContext cp(activity.get());
// Now instantiate the python instance.. pass args if some were provided, or
// an empty dict otherwise.
PythonRef args;
if (settings_obj == Py_None) {
args.Steal(Py_BuildValue("({})"));
} else {
args.Steal(Py_BuildValue("(O)", settings_obj));
}
PythonRef result = activity_type.Call(args);
if (!result.exists()) {
throw Exception("HostActivity creation failed");
}
// If all went well, the python activity constructor should have called
// _ba.register_activity(), so we should be able to get at the same python
// activity we just instantiated through the c++ class.
if (activity->GetPyActivity() != result.get()) {
throw Exception("Error on HostActivity construction");
}
PyObject* obj = result.get();
Py_INCREF(obj);
return obj;
}
auto HostSession::RegisterPyActivity(PyObject* activity_obj) -> HostActivity* {
// The context should be pointing to an unregistered HostActivity;
// register and return it.
HostActivity* activity = Context::current().GetHostActivity();
if (!activity)
throw Exception(
"No current activity in RegisterPyActivity; did you remember to call "
"ba.newHostActivity() to instantiate your activity?");
activity->RegisterPyActivity(activity_obj);
return activity;
}
void HostSession::DecrementPlayerTimeOuts(millisecs_t millisecs) {
for (auto&& i : players_) {
Player* player = i.get();
assert(player);
if (player->time_out() < millisecs) {
std::string kick_str =
g_game->GetResourceString("kickIdlePlayersKickedText");
Utils::StringReplaceOne(&kick_str, "${NAME}", player->GetName());
ScreenMessage(kick_str);
RemovePlayer(player);
return; // Bail for this round since we prolly mucked with the list.
} else if (player->time_out() > BA_PLAYER_TIME_OUT_WARN
&& (player->time_out() - millisecs <= BA_PLAYER_TIME_OUT_WARN)) {
std::string kick_str_1 =
g_game->GetResourceString("kickIdlePlayersWarning1Text");
Utils::StringReplaceOne(&kick_str_1, "${NAME}", player->GetName());
Utils::StringReplaceOne(&kick_str_1, "${COUNT}",
std::to_string(BA_PLAYER_TIME_OUT_WARN / 1000));
ScreenMessage(kick_str_1);
ScreenMessage(g_game->GetResourceString("kickIdlePlayersWarning2Text"));
}
player->set_time_out(player->time_out() - millisecs);
}
}
void HostSession::ProcessPlayerTimeOuts() {
millisecs_t real_time = GetRealTime();
if (foreground_host_activity_.exists()
&& foreground_host_activity_->game_speed() > 0.0
&& !foreground_host_activity_->paused()
&& foreground_host_activity_->getAllowKickIdlePlayers()
&& kick_idle_players_) {
// Let's only do this every now and then.
if (real_time - last_kick_idle_players_decrement_time_ > 1000) {
DecrementPlayerTimeOuts(real_time
- last_kick_idle_players_decrement_time_);
last_kick_idle_players_decrement_time_ = real_time;
}
} else {
// If we're not kicking, we still store the latest time (so it doesnt
// accumulate for when we start again).
last_kick_idle_players_decrement_time_ = real_time;
}
}
void HostSession::StepScene() {
// Run up our game-time timers.
sim_timers_.Run(scene()->time());
// And step.
scene()->Step();
}
void HostSession::Update(int time_advance) {
assert(InGameThread());
// We can be killed at any time, so let's keep an eye out for that.
WeakRef<HostSession> test_ref(this);
assert(test_ref.exists());
ProcessPlayerTimeOuts();
GameStream* output_stream = GetGameStream();
// Advance base time by the specified amount,
// stopping at all timers along the way.
millisecs_t target_base_time = base_time_ + time_advance;
while (!base_timers_.empty()
&& (base_time_ + base_timers_.GetTimeToNextExpire(base_time_)
<= target_base_time)) {
base_time_ += base_timers_.GetTimeToNextExpire(base_time_);
if (output_stream) {
output_stream->SetTime(base_time_);
}
base_timers_.Run(base_time_);
}
base_time_ = target_base_time;
if (output_stream) {
output_stream->SetTime(base_time_);
}
assert(test_ref.exists());
// Update our activities (iterate via weak-refs as this list may change under
// us at any time).
std::vector<Object::WeakRef<HostActivity> > activities =
PointersToWeakRefs(RefsToPointers(host_activities_));
for (auto&& i : activities) {
if (i.exists()) {
i->Update(time_advance);
assert(test_ref.exists());
}
}
assert(test_ref.exists());
// Periodically prune various dead refs.
if (base_time_ > next_prune_time_) {
PruneDeadMapRefs(&textures_);
PruneDeadMapRefs(&sounds_);
PruneDeadMapRefs(&models_);
PruneDeadRefs(&python_calls_);
next_prune_time_ = base_time_ + 5000;
}
assert(test_ref.exists());
}
HostSession::~HostSession() {
try {
shutting_down_ = true;
// Put the scene in shut-down mode before we start killing stuff
// (this generates warnings, suppresses messages, etc).
scene_->set_shutting_down(true);
// Clear out all python calls registered in our context
// (should wipe out refs to our session and prevent them from running
// without a valid session context).
for (auto&& i : python_calls_) {
if (i.exists()) {
i->MarkDead();
}
}
// Mark all our media dead to clear it out of our output-stream cleanly.
for (auto&& i : textures_) {
if (i.second.exists()) {
i.second->MarkDead();
}
}
for (auto&& i : models_) {
if (i.second.exists()) {
i.second->MarkDead();
}
}
for (auto&& i : sounds_) {
if (i.second.exists()) {
i.second->MarkDead();
}
}
// Clear our timers and scene; this should wipe out any remaining refs
// to our session scene.
base_timers_.Clear();
sim_timers_.Clear();
scene_.Clear();
// Kill our python session object.
{
ScopedSetContext cp(this);
session_py_obj_.Release();
}
// Kill any remaining activity data. Generally all activities should die
// when the session python object goes down, but lets clean up in case any
// didn't.
for (auto&& i : host_activities_) {
ScopedSetContext cp{Object::Ref<ContextTarget>(i)};
i.Clear();
}
// Report outstanding calls. There shouldn't be any at this point. Actually
// it turns out there's generally 1; whichever call was responsible for
// killing this activity will still be in progress.. so let's report on 2 or
// more I guess.
#if BA_DEBUG_BUILD
PruneDeadRefs(&python_calls_);
if (python_calls_.size() > 1) {
std::string s = "WARNING: " + std::to_string(python_calls_.size())
+ " live PythonContextCalls at shutdown for "
+ "HostSession" + " (1 call is expected):";
int count = 1;
for (auto&& i : python_calls_) {
s += ("\n " + std::to_string(count++) + ": "
+ i->GetObjectDescription());
}
Log(s);
}
#endif // BA_DEBUG_BUILD
} catch (const std::exception& e) {
Log("Exception in HostSession destructor: " + std::string(e.what()));
}
}
void HostSession::RegisterCall(PythonContextCall* call) {
assert(call);
python_calls_.emplace_back(call);
// If we're shutting down, just kill the call immediately.
// (we turn all of our calls to no-ops as we shut down).
if (shutting_down_) {
Log("WARNING: adding call to expired session; call will not function: "
+ call->GetObjectDescription());
call->MarkDead();
}
}
auto HostSession::GetUnusedPlayerName(Player* p, const std::string& base_name)
-> std::string {
// Now find the first non-taken variation.
int index = 1;
std::string name_test;
while (true) {
if (index > 1) {
name_test = base_name + " " + std::to_string(index);
} else {
name_test = base_name;
}
bool name_found = false;
for (auto&& j : players_) {
if ((j->GetName() == name_test) && (j.get() != p)) {
name_found = true;
break;
}
}
if (!name_found) break;
index += 1;
}
return name_test;
}
void HostSession::DumpFullState(GameStream* out) {
// Add session-scene.
if (scene_.exists()) {
scene_->Dump(out);
}
// Dump media associated with session-scene.
for (auto&& i : textures_) {
if (Texture* t = i.second.get()) {
out->AddTexture(t);
}
}
for (auto&& i : sounds_) {
if (Sound* s = i.second.get()) {
out->AddSound(s);
}
}
for (auto&& i : models_) {
if (Model* s = i.second.get()) {
out->AddModel(s);
}
}
// Dump session-scene's nodes.
if (scene_.exists()) {
scene_->DumpNodes(out);
}
// Now let our activities dump themselves.
for (auto&& i : host_activities_) {
i->DumpFullState(out);
}
}
void HostSession::GetCorrectionMessages(
bool blend, std::vector<std::vector<uint8_t> >* messages) {
std::vector<uint8_t> message;
// Grab correction for session scene (though there shouldn't be one).
if (scene_.exists()) {
message = scene_->GetCorrectionMessage(blend);
if (message.size() > 4) {
// A correction packet of size 4 is empty; ignore it.
messages->push_back(message);
}
}
// Now do same for activity scenes.
for (auto&& i : host_activities_) {
if (HostActivity* ha = i.get()) {
if (Scene* sg = ha->scene()) {
message = sg->GetCorrectionMessage(blend);
if (message.size() > 4) {
// A correction packet of size 4 is empty; ignore it.
messages->push_back(message);
}
}
}
}
}
auto HostSession::NewTimer(TimeType timetype, TimerMedium length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int {
// Make sure the runnable passed in is reference-managed already
// (we may not add an initial reference ourself).
assert(runnable->is_valid_refcounted_object());
// We currently support game and base timers.
switch (timetype) {
case TimeType::kSim:
case TimeType::kBase:
// Game and base timers are the same thing for us.
return NewTimer(length, repeat, runnable);
default:
// Gall back to default for descriptive error otherwise.
return ContextTarget::NewTimer(timetype, length, repeat, runnable);
}
}
void HostSession::DeleteTimer(TimeType timetype, int timer_id) {
switch (timetype) {
case TimeType::kSim:
case TimeType::kBase:
// Game and base timers are the same thing for us.
DeleteTimer(timer_id);
break;
default:
// Fall back to default for descriptive error otherwise.
ContextTarget::DeleteTimer(timetype, timer_id);
break;
}
}
auto HostSession::GetTime(TimeType timetype) -> millisecs_t {
switch (timetype) {
case TimeType::kSim:
case TimeType::kBase:
return scene_->time();
default:
// Fall back to default for descriptive error otherwise.
return ContextTarget::GetTime(timetype);
}
}
} // namespace ballistica

View File

@ -1,147 +0,0 @@
// Copyright (c) 2011-2020 Eric Froemling
#include "ballistica/game/session/net_client_session.h"
#include <algorithm>
#include <vector>
#include "ballistica/app/app_globals.h"
#include "ballistica/game/connection/connection_to_host.h"
#include "ballistica/media/media_server.h"
namespace ballistica {
NetClientSession::NetClientSession() {
// Sanity check: we should only ever be writing one replay at once.
if (g_app_globals->replay_open) {
Log("ERROR: g_replay_open true at netclient start; shouldn't happen.");
}
assert(g_media_server);
g_media_server->PushBeginWriteReplayCall();
writing_replay_ = true;
g_app_globals->replay_open = true;
}
NetClientSession::~NetClientSession() {
if (writing_replay_) {
// Sanity check: we should only ever be writing one replay at once.
if (!g_app_globals->replay_open) {
Log("ERROR: g_replay_open false at net-client close; shouldn't happen.");
}
g_app_globals->replay_open = false;
assert(g_media_server);
g_media_server->PushEndWriteReplayCall();
writing_replay_ = false;
}
}
void NetClientSession::SetConnectionToHost(ConnectionToHost* c) {
connection_to_host_ = c;
}
void NetClientSession::OnCommandBufferUnderrun() {
// Any time we run out of data, hit the brakes on our playback speed.
// Update: maybe not.
// correction_ *= 0.99f;
}
void NetClientSession::Update(int time_advance) {
if (shutting_down_) {
return;
}
// Now do standard step.
ClientSession::Update(time_advance);
// And update our timing to try and ensure we don't run out of buffer.
UpdateBuffering();
}
void NetClientSession::UpdateBuffering() {
// if (NetGraph *graph = g_graphics->debug_graph_1()) {
// graph->addSample(GetRealTime(), steps_on_list_);
// }
// Keep record of the most and least amount of time we've had buffered
// recently, and slow down/speed up a bit based on that.
{
int bucket_count = static_cast<int>(least_buffered_count_list_.size());
// Change bucket every g_delay_samples samples.
int bucket = (buffer_count_list_index_ / g_app_globals->delay_samples)
% bucket_count;
int bucket_iteration =
buffer_count_list_index_ % g_app_globals->delay_samples;
// *Set* the value the first iteration in each bucket; do *min* after that.
if (bucket_iteration == 0) {
least_buffered_count_list_[bucket] = steps_on_list_;
most_buffered_count_list_[bucket] = steps_on_list_;
} else {
least_buffered_count_list_[bucket] =
std::min(least_buffered_count_list_[bucket], steps_on_list_);
most_buffered_count_list_[bucket] =
std::max(most_buffered_count_list_[bucket], steps_on_list_);
// After the last sample in each bucket, feed the max bucket value in
// as the 'low pass' buffer-count. The low-pass curve minus our largest
// spike value should be where we want to aim for in the buffer.
if (bucket_iteration == g_app_globals->delay_samples - 1) {
float smoothing = 0.5f;
low_pass_smoothed_ =
smoothing * low_pass_smoothed_
+ (1.0f - smoothing)
* static_cast<float>(most_buffered_count_list_[bucket]);
}
}
// Keep track of the largest min/max difference in our sample segments.
int largest_spike = 0;
buffer_count_list_index_++;
for (int i = 1; i < bucket_count; i++) {
int spike = most_buffered_count_list_[i] - least_buffered_count_list_[i];
if (spike > largest_spike) {
largest_spike = spike;
}
}
// Slowly adjust largest spike value based on the biggest in recent history.
{
float smoothing = 0.95f;
largest_spike_smoothed_ =
smoothing * largest_spike_smoothed_
+ (1.0f - smoothing) * static_cast<float>(largest_spike);
}
// Low pass is the most buffered data we've had in the most recent slot.
float ideal_offset = low_pass_smoothed_ - largest_spike_smoothed_ * 1.0f;
// Any time we've got no current buffered data, slow down fast.
// (otherwise we can get stuck cruising along with no 0 buffered data and
// things get real jerky looking)
if (steps_on_list_ == 0) {
ideal_offset -= 100.0f;
}
float smoothing = 0.0f;
correction_ = smoothing * correction_
+ (1.0f - smoothing) * (1.0f + 0.002f * ideal_offset);
correction_ = std::min(1.5f, std::max(0.5f, correction_));
// if (NetGraph *graph = g_graphics->debug_graph_2()) {
// graph->addSample(GetRealTime(), correction_);
// }
}
}
void NetClientSession::HandleSessionMessage(
const std::vector<uint8_t>& message) {
// Do the standard thing, but also write this message straight to our replay
// stream if we have one.
ClientSession::HandleSessionMessage(message);
if (writing_replay_) {
assert(g_media_server);
g_media_server->PushAddMessageToReplayCall(message);
}
}
} // namespace ballistica

View File

@ -1,321 +0,0 @@
// Copyright (c) 2011-2020 Eric Froemling
#include "ballistica/game/session/replay_client_session.h"
#include <cmath>
#include <string>
#include <utility>
#include <vector>
#include "ballistica/dynamics/material/material.h"
#include "ballistica/game/connection/connection_to_client.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/generic/huffman.h"
#include "ballistica/generic/utils.h"
#include "ballistica/math/vector3f.h"
#include "ballistica/networking/networking.h"
#include "ballistica/platform/platform.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
auto ReplayClientSession::GetActualTimeAdvance(int advance_in) -> int {
return static_cast<int>(
round(advance_in * pow(2.0f, g_game->replay_speed_exponent())));
}
ReplayClientSession::ReplayClientSession(std::string filename)
: file_name_(std::move(filename)),
file_(nullptr),
message_fetch_num_(0),
have_sent_client_message_(false) {
// take responsibility for feeding all clients to this device..
g_game->RegisterClientController(this);
// go ahead and just do a reset here, which will get things going..
Reset(true);
}
ReplayClientSession::~ReplayClientSession() {
// we no longer are responsible for feeding clients to this device..
g_game->UnregisterClientController(this);
if (file_) {
fclose(file_);
file_ = nullptr;
}
}
void ReplayClientSession::OnClientConnected(ConnectionToClient* c) {
// sanity check - abort if its on either of our lists already
for (ConnectionToClient* i : connections_to_clients_) {
if (i == c) {
Log("Error: ReplayClientSession::OnClientConnected()"
" got duplicate connection");
return;
}
}
for (ConnectionToClient* i : connections_to_clients_ignored_) {
if (i == c) {
Log("Error: ReplayClientSession::OnClientConnected()"
" got duplicate connection");
return;
}
}
// if we've sent *any* commands out to clients so far, we currently have to
// ignore new connections (need to rebuild state to match current session
// state)
{
connections_to_clients_.push_back(c);
// we create a temporary output stream just for the purpose of building
// a giant session-commands message that we can send to the client
// to build its state up to where we are currently.
GameStream out(nullptr, false);
// go ahead and dump our full state..
DumpFullState(&out);
// grab the message that's been built up..
// if its not empty, send it to the client.
std::vector<uint8_t> out_message = out.GetOutMessage();
if (!out_message.empty()) c->SendReliableMessage(out_message);
// also send a correction packet to sync up all our dynamics
// (technically could do this *just* for the new client)
{
std::vector<std::vector<uint8_t> > messages;
bool blend = false;
GetCorrectionMessages(blend, &messages);
// FIXME - have to send reliably at the moment since these will most
// likely be bigger than our unreliable packet limit.. :-(
for (auto&& i : messages) {
for (auto&& j : connections_to_clients_) {
j->SendReliableMessage(i);
}
}
}
}
}
void ReplayClientSession::OnClientDisconnected(ConnectionToClient* c) {
// search for it on either our ignored or regular lists..
for (auto i = connections_to_clients_.begin();
i != connections_to_clients_.end(); i++) {
if (*i == c) {
connections_to_clients_.erase(i);
return;
}
}
for (auto i = connections_to_clients_ignored_.begin();
i != connections_to_clients_ignored_.end(); i++) {
if (*i == c) {
connections_to_clients_ignored_.erase(i);
return;
}
}
Log("Error: ReplayClientSession::OnClientDisconnected()"
" called for connection not on lists");
}
void ReplayClientSession::FetchMessages() {
if (!file_ || shutting_down()) {
return;
}
// If we have no messages left, read from the file until we get some.
while (commands_.empty()) {
std::vector<uint8_t> buffer;
uint8_t len8;
uint32_t len32;
// read the size of the message..
// the first byte represents the actual size if the value is < 254
// if it is 254, the 2 bytes after it represent size
// if it is 255, the 4 bytes after it represent size
if (fread(&len8, 1, 1, file_) != 1) {
// so they know to be done when they reach the end of the command list
// (instead of just waiting for more commands)
commands_.emplace_back(1,
static_cast<uint8_t>(SessionCommand::kEndOfFile));
fclose(file_);
file_ = nullptr;
return;
}
if (len8 < 254) {
len32 = len8;
} else {
// pull 16 bit len..
if (len8 == 254) {
uint16_t len16;
if (fread(&len16, 2, 1, file_) != 1) {
// so they know to be done when they reach the end of the command list
// (instead of just waiting for more commands)
commands_.emplace_back(
1, static_cast<uint8_t>(SessionCommand::kEndOfFile));
fclose(file_);
file_ = nullptr;
return;
}
assert(len16 >= 254);
len32 = len16;
} else {
// pull 32 bit len...
if (fread(&len32, 4, 1, file_) != 1) {
// so they know to be done when they reach the end of the command list
// (instead of just waiting for more commands)
commands_.emplace_back(
1, static_cast<uint8_t>(SessionCommand::kEndOfFile));
fclose(file_);
file_ = nullptr;
return;
}
assert(len32 > 65535);
}
}
// read and decompress the actual message..
BA_PRECONDITION(len32 > 0);
buffer.resize(len32);
if (fread(&(buffer[0]), len32, 1, file_) != 1) {
commands_.emplace_back(1,
static_cast<uint8_t>(SessionCommand::kEndOfFile));
fclose(file_);
file_ = nullptr;
return;
}
std::vector<uint8_t> data_decompressed =
g_utils->huffman()->decompress(buffer);
HandleSessionMessage(data_decompressed);
// Also send it to all client-connections we're attached to.
// NOTE: We currently are sending everything as reliable; we can maybe do
// unreliable for certain type of messages. Though perhaps when passing
// around replays maybe its best to keep everything intact.
have_sent_client_message_ = true;
for (auto&& i : connections_to_clients_) {
i->SendReliableMessage(data_decompressed);
}
message_fetch_num_++;
}
}
void ReplayClientSession::Error(const std::string& description) {
// Close the replay, announce something went wrong with it, and then do
// standard error response..
ScreenMessage(g_game->GetResourceString("replayReadErrorText"), {1, 0, 0});
if (file_) {
fclose(file_);
file_ = nullptr;
}
ClientSession::Error(description);
}
void ReplayClientSession::OnReset(bool rewind) {
// Handles base resetting.
ClientSession::OnReset(rewind);
// If we've got any clients attached to us, tell them to reset as well.
for (auto&& i : connections_to_clients_) {
i->SendReliableMessage(std::vector<uint8_t>(1, BA_MESSAGE_SESSION_RESET));
}
// If rewinding, pop back to the start of our file.
if (rewind) {
if (file_) {
fclose(file_);
file_ = nullptr;
}
file_ = g_platform->FOpen(file_name_.c_str(), "rb");
if (!file_) {
Error("can't open file for reading");
return;
}
// Read file ID and version to make sure we support this file.
uint32_t file_id;
if ((fread(&file_id, sizeof(file_id), 1, file_) != 1)) {
Error("error reading file_id");
return;
}
if (file_id != kBrpFileID) {
Error("incorrect file_id");
return;
}
// Make sure its a compatible protocol version.
uint16_t version;
if (fread(&version, sizeof(version), 1, file_) != 1) {
Error("error reading version");
return;
}
if (version > kProtocolVersion || version < kProtocolVersionMin) {
ScreenMessage(g_game->GetResourceString("replayVersionErrorText"),
{1, 0, 0});
End();
return;
}
}
}
void ReplayClientSession::DumpFullState(GameStream* out) {
// This shouldn't actually be replay-specific. Should move this up to
// ClientSession perhaps?
// Add all scenes.
for (auto&& i : scenes_) {
if (Scene* sg = i.get()) {
sg->Dump(out);
}
}
// Before doing any nodes, we need to create all materials.
// (but *not* their components, which may reference the nodes that we haven't
// made yet)
for (auto&& i : materials_) {
if (Material* m = i.get()) {
out->AddMaterial(m);
}
}
// Add all media.
for (auto&& i : textures_) {
if (Texture* t = i.get()) {
out->AddTexture(t);
}
}
for (auto&& i : models_) {
if (Model* s = i.get()) {
out->AddModel(s);
}
}
for (auto&& i : sounds_) {
if (Sound* s = i.get()) {
out->AddSound(s);
}
}
for (auto&& i : collide_models_) {
if (CollideModel* s = i.get()) {
out->AddCollideModel(s);
}
}
// Add all scene nodes.
for (auto&& i : scenes_) {
if (Scene* sg = i.get()) {
sg->DumpNodes(out);
}
}
// Now fill out materials since all the nodes/etc they reference exist.
for (auto&& i : materials_) {
if (Material* m = i.get()) {
m->DumpComponents(out);
}
}
}
} // namespace ballistica

View File

@ -1,36 +0,0 @@
// Copyright (c) 2011-2020 Eric Froemling
#include "ballistica/game/session/session.h"
#include "ballistica/app/app_globals.h"
#include "ballistica/game/game.h"
namespace ballistica {
Session::Session() {
g_app_globals->session_count++;
// new sessions immediately become foreground
g_game->SetForegroundSession(this);
}
Session::~Session() { g_app_globals->session_count--; }
void Session::Update(int time_advance) {}
auto Session::GetForegroundContext() -> Context { return Context(); }
void Session::Draw(FrameDef*) {}
void Session::ScreenSizeChanged() {}
void Session::LanguageChanged() {}
void Session::GraphicsQualityChanged(GraphicsQuality q) {}
void Session::DebugSpeedMultChanged() {}
void Session::DumpFullState(GameStream* out) {
Log("Session::DumpFullState() being called; shouldn't happen.");
}
} // namespace ballistica