mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-24 07:53:30 +08:00
Yet more C++ stuff
This commit is contained in:
parent
8c446dd0d6
commit
18ec4dc264
@ -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"
|
||||
}
|
||||
@ -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
@ -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
|
||||
@ -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_;
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -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
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user