261 lines
7.7 KiB
C++

// Released under the MIT License. See LICENSE for details.
#include "ballistica/shared/foundation/object.h"
#include <mutex>
#include <unordered_map>
#include "ballistica/core/core.h"
#include "ballistica/core/platform/core_platform.h"
#include "ballistica/core/support/base_soft.h"
#include "ballistica/shared/generic/utils.h"
namespace ballistica {
// Note: Functionality here assumes that someone has imported core and will
// fail hard if not (hence us using core's internal globals below).
using core::g_base_soft;
using core::g_core;
Object::Object() {
#if BA_DEBUG_BUILD
// Mark when we were born.
// NOTE: Using core's internal globals here; don't do this normally.
assert(g_core);
object_birth_time_ = g_core->GetAppTimeMillisecs();
// Add ourself to the global object list.
{
std::scoped_lock lock(g_core->object_list_mutex);
object_prev_ = nullptr;
object_next_ = g_core->object_list_first;
g_core->object_list_first = this;
if (object_next_) {
object_next_->object_prev_ = this;
}
g_core->object_count++;
}
#endif // BA_DEBUG_BUILD
}
Object::~Object() {
#if BA_DEBUG_BUILD
{
// NOTE: using core's internal globals here; don't do this normally!
assert(g_core);
// Pull ourself from the global obj list.
std::scoped_lock lock(g_core->object_list_mutex);
if (object_next_) {
object_next_->object_prev_ = object_prev_;
}
if (object_prev_) {
object_prev_->object_next_ = object_next_;
} else {
g_core->object_list_first = object_next_;
}
g_core->object_count--;
}
// Objects should never be dying with non-zero reference counts.
if (object_strong_ref_count_ != 0) {
FatalError("Object is dying with non-zero ref-count.");
}
// Objects set up as ref-counted shouldn't be dying before getting reffed.
if (object_is_ref_counted_ && !object_has_been_strong_reffed_) {
FatalError(
"Object set as ref-counted but dying without ever having a ref.");
}
#endif // BA_DEBUG_BUILD
// Invalidate all our weak refs.
// We could call Release() on each but we'd have to deactivate the
// thread-check since virtual functions won't work as expected in a
// destructor. Also we can take a few shortcuts here since we know
// we're deleting the entire list, not just one object.
while (object_weak_refs_) {
auto tmp{object_weak_refs_};
object_weak_refs_ = tmp->next_;
tmp->prev_ = nullptr;
tmp->next_ = nullptr;
tmp->obj_ = nullptr;
}
}
auto Object::GetObjectTypeName() const -> std::string {
// Default implementation just returns type name.
// Note: using core's globals directly; don't normally do this!
if (g_core) {
return g_core->platform->DemangleCXXSymbol(typeid(*this).name());
}
return "(unknown-no-core)";
}
auto Object::GetObjectDescription() const -> std::string {
return "<" + GetObjectTypeName() + " object at " + Utils::PtrToString(this)
+ ">";
}
auto Object::GetDefaultOwnerThread() const -> EventLoopID {
return EventLoopID::kLogic;
}
auto Object::GetThreadOwnership() const -> Object::ThreadOwnership {
#if BA_DEBUG_BUILD
return thread_ownership_;
#else
FatalError("Should not be called in release builds.");
return ThreadOwnership::kClassDefault;
#endif
}
void Object::LsObjects() {
#if BA_DEBUG_BUILD
// Note: using core's internal globals here; don't normally do this.
assert(g_core);
std::string s;
{
std::scoped_lock lock(g_core->object_list_mutex);
s = std::to_string(g_core->object_count) + " Objects at time "
+ std::to_string(g_core->GetAppTimeMillisecs()) + ";";
if (explicit_bool(true)) {
std::unordered_map<std::string, int> obj_map;
// Tally up counts for all types.
int count = 0;
for (Object* o = g_core->object_list_first; o != nullptr;
o = o->object_next_) {
count++;
std::string obj_name = o->GetObjectTypeName();
auto i = obj_map.find(obj_name);
if (i == obj_map.end()) {
obj_map[obj_name] = 1;
} else {
// Getting complaints that 'second' is unused, but we sort and print
// using this value like 10 lines down. Hmmm.
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnusedValue"
i->second++;
#pragma clang diagnostic pop
}
}
// Now sort them by count and print.
std::vector<std::pair<int, std::string> > sorted;
sorted.reserve(obj_map.size());
for (auto&& i : obj_map) {
sorted.emplace_back(i.second, i.first);
}
std::sort(sorted.rbegin(), sorted.rend());
for (auto&& i : sorted) {
s += "\n " + std::to_string(i.first) + ": " + i.second;
}
assert(count == g_core->object_count);
}
}
Log(LogLevel::kInfo, s);
#else
Log(LogLevel::kInfo, "LsObjects() only functions in debug builds.");
#endif // BA_DEBUG_BUILD
}
#if BA_DEBUG_BUILD
static auto GetCurrentEventLoopID() -> EventLoopID {
if (g_core->InMainThread()) {
return EventLoopID::kMain;
} else if (g_base_soft && g_base_soft->InLogicThread()) {
return EventLoopID::kLogic;
} else if (g_base_soft && g_base_soft->InAudioThread()) {
return EventLoopID::kAudio;
} else if (g_base_soft && g_base_soft->InNetworkWriteThread()) {
return EventLoopID::kNetworkWrite;
} else if (g_base_soft && g_base_soft->InAssetsThread()) {
return EventLoopID::kAssets;
} else if (g_base_soft && g_base_soft->InBGDynamicsThread()) {
return EventLoopID::kBGDynamics;
} else {
throw Exception(std::string("unrecognized thread: ") + CurrentThreadName());
}
}
void Object::ObjectUpdateForAcquire() {
ThreadOwnership thread_ownership = GetThreadOwnership();
// If we're set to use the next-referencing thread and haven't set one
// yet, do so.
if (thread_ownership == ThreadOwnership::kNextReferencing
&& owner_thread_ == EventLoopID::kInvalid) {
owner_thread_ = GetCurrentEventLoopID();
}
}
void Object::ObjectThreadCheck() {
if (!thread_checks_enabled_) {
return;
}
ThreadOwnership thread_ownership = GetThreadOwnership();
// Special case; graphics context (not simply a thread so have to handle
// specially).
if (thread_ownership == ThreadOwnership::kGraphicsContext) {
if (!(g_base_soft && g_base_soft->InGraphicsContext())) {
throw Exception("ObjectThreadCheck failed for " + GetObjectDescription()
+ "; expected graphics context.");
}
return;
}
EventLoopID t;
if (thread_ownership == ThreadOwnership::kClassDefault) {
t = GetDefaultOwnerThread();
} else {
t = owner_thread_;
}
#define DO_FAIL(THREADNAME) \
throw Exception("ObjectThreadCheck failed for " + GetObjectDescription() \
+ "; expected " THREADNAME " thread; got " \
+ CurrentThreadName())
switch (t) {
case EventLoopID::kMain:
if (!g_core->InMainThread()) {
DO_FAIL("Main");
}
break;
case EventLoopID::kLogic:
if (!(g_base_soft && g_base_soft->InLogicThread())) {
DO_FAIL("Logic");
}
break;
case EventLoopID::kAudio:
if (!(g_base_soft && g_base_soft->InAudioThread())) {
DO_FAIL("Audio");
}
break;
case EventLoopID::kNetworkWrite:
if (!(g_base_soft && g_base_soft->InNetworkWriteThread())) {
DO_FAIL("NetworkWrite");
}
break;
case EventLoopID::kAssets:
if (!(g_base_soft && g_base_soft->InAssetsThread())) {
DO_FAIL("Assets");
}
break;
case EventLoopID::kBGDynamics:
if (!(g_base_soft && g_base_soft->InBGDynamicsThread())) {
DO_FAIL("BGDynamics");
}
break;
default:
throw Exception();
}
#undef DO_FAIL
}
#endif // BA_DEBUG_BUILD
} // namespace ballistica