mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-03-21 17:16:32 +08:00
1226 lines
43 KiB
C++
1226 lines
43 KiB
C++
// Released under the MIT License. See LICENSE for details.
|
|
|
|
#include "ballistica/python/methods/python_methods_app.h"
|
|
|
|
#include "ballistica/app/app.h"
|
|
#include "ballistica/app/app_globals.h"
|
|
#include "ballistica/core/logging.h"
|
|
#include "ballistica/game/connection/connection_set.h"
|
|
#include "ballistica/game/game_stream.h"
|
|
#include "ballistica/game/host_activity.h"
|
|
#include "ballistica/game/session/host_session.h"
|
|
#include "ballistica/game/session/replay_client_session.h"
|
|
#include "ballistica/graphics/graphics.h"
|
|
#include "ballistica/media/component/texture.h"
|
|
#include "ballistica/python/class/python_class_activity_data.h"
|
|
#include "ballistica/python/class/python_class_session_data.h"
|
|
#include "ballistica/python/python.h"
|
|
#include "ballistica/python/python_context_call_runnable.h"
|
|
#include "ballistica/scene/scene.h"
|
|
|
|
namespace ballistica {
|
|
|
|
// Python does lots of signed bitwise stuff; turn off those warnings here.
|
|
#pragma clang diagnostic push
|
|
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
|
#pragma ide diagnostic ignored "RedundantCast"
|
|
|
|
auto PyAppName(PyObject* self) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("app_name");
|
|
|
|
// This will get subbed out by standard filtering.
|
|
return PyUnicode_FromString("ballisticacore");
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyAppNameUpper(PyObject* self) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("app_name_upper");
|
|
|
|
// This will get subbed out by standard filtering.
|
|
return PyUnicode_FromString("BallisticaCore");
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyIsXCodeBuild(PyObject* self) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("is_xcode_build");
|
|
if (g_buildconfig.xcode_build()) {
|
|
Py_RETURN_TRUE;
|
|
}
|
|
Py_RETURN_FALSE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyCanDisplayFullUnicode(PyObject* self) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("can_display_full_unicode");
|
|
if (g_buildconfig.enable_os_font_rendering()) {
|
|
Py_RETURN_TRUE;
|
|
}
|
|
Py_RETURN_FALSE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyGetSession(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("get_session");
|
|
int raise = true;
|
|
static const char* kwlist[] = {"doraise", nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|i",
|
|
const_cast<char**>(kwlist), &raise)) {
|
|
return nullptr;
|
|
}
|
|
if (HostSession* hs = Context::current().GetHostSession()) {
|
|
PyObject* obj = hs->GetSessionPyObj();
|
|
if (obj) {
|
|
Py_INCREF(obj);
|
|
return obj;
|
|
}
|
|
} else {
|
|
if (raise) {
|
|
throw Exception(PyExcType::kSessionNotFound);
|
|
}
|
|
}
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyNewHostSession(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("new_host_session");
|
|
const char* benchmark_type_str = nullptr;
|
|
static const char* kwlist[] = {"sessiontype", "benchmark_type", nullptr};
|
|
PyObject* sessiontype_obj;
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|s",
|
|
const_cast<char**>(kwlist), &sessiontype_obj,
|
|
&benchmark_type_str)) {
|
|
return nullptr;
|
|
}
|
|
BenchmarkType benchmark_type = BenchmarkType::kNone;
|
|
if (benchmark_type_str != nullptr) {
|
|
if (!strcmp(benchmark_type_str, "cpu")) {
|
|
benchmark_type = BenchmarkType::kCPU;
|
|
} else if (!strcmp(benchmark_type_str, "gpu")) {
|
|
benchmark_type = BenchmarkType::kGPU;
|
|
} else {
|
|
throw Exception(
|
|
"Invalid benchmark type: '" + std::string(benchmark_type_str) + "'",
|
|
PyExcType::kValue);
|
|
}
|
|
}
|
|
g_game->LaunchHostSession(sessiontype_obj, benchmark_type);
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyNewReplaySession(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("new_replay_session");
|
|
std::string file_name;
|
|
PyObject* file_name_obj;
|
|
static const char* kwlist[] = {"file_name", nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(
|
|
args, keywds, "O", const_cast<char**>(kwlist), &file_name_obj)) {
|
|
return nullptr;
|
|
}
|
|
file_name = Python::GetPyString(file_name_obj);
|
|
g_game->LaunchReplaySession(file_name);
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyIsInReplay(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("is_in_replay");
|
|
BA_PRECONDITION(InGameThread());
|
|
static const char* kwlist[] = {nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
|
const_cast<char**>(kwlist))) {
|
|
return nullptr;
|
|
}
|
|
if (dynamic_cast<ReplayClientSession*>(g_game->GetForegroundSession())) {
|
|
Py_RETURN_TRUE;
|
|
} else {
|
|
Py_RETURN_FALSE;
|
|
}
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyRegisterSession(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("register_session");
|
|
assert(InGameThread());
|
|
PyObject* session_obj;
|
|
static const char* kwlist[] = {"session", nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
|
const_cast<char**>(kwlist), &session_obj)) {
|
|
return nullptr;
|
|
}
|
|
HostSession* hsc = Context::current().GetHostSession();
|
|
if (!hsc) {
|
|
throw Exception("No HostSession found.");
|
|
}
|
|
|
|
// Store our py obj with our HostSession and return
|
|
// the HostSession to be stored with our py obj.
|
|
hsc->RegisterPySession(session_obj);
|
|
return PythonClassSessionData::Create(hsc);
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyRegisterActivity(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("register_activity");
|
|
assert(InGameThread());
|
|
PyObject* activity_obj;
|
|
static const char* kwlist[] = {"activity", nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
|
const_cast<char**>(kwlist), &activity_obj)) {
|
|
return nullptr;
|
|
}
|
|
HostSession* hs = Context::current().GetHostSession();
|
|
if (!hs) {
|
|
throw Exception("No HostSession found");
|
|
}
|
|
|
|
// Generate and return an ActivityData for this guy..
|
|
// (basically just a link to its C++ equivalent).
|
|
return PythonClassActivityData::Create(hs->RegisterPyActivity(activity_obj));
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyGetForegroundHostSession(PyObject* self, PyObject* args,
|
|
PyObject* keywds) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("get_foreground_host_session");
|
|
static const char* kwlist[] = {nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "",
|
|
const_cast<char**>(kwlist))) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Note: we return None if not in the game thread.
|
|
HostSession* s = InGameThread()
|
|
? g_game->GetForegroundContext().GetHostSession()
|
|
: nullptr;
|
|
if (s != nullptr) {
|
|
PyObject* obj = s->GetSessionPyObj();
|
|
Py_INCREF(obj);
|
|
return obj;
|
|
}
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyNewActivity(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("new_activity");
|
|
|
|
static const char* kwlist[] = {"activity_type", "settings", nullptr};
|
|
PyObject* activity_type_obj;
|
|
PyObject* settings_obj = Py_None;
|
|
PythonRef settings;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|O",
|
|
const_cast<char**>(kwlist),
|
|
&activity_type_obj, &settings_obj)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// If they passed a settings dict, make a shallow copy of it (so we dont
|
|
// inadvertently mess up level lists or whatever the settings came from).
|
|
if (settings_obj != Py_None) {
|
|
if (!PyDict_Check(settings_obj)) {
|
|
throw Exception("Expected a dict for settings.", PyExcType::kType);
|
|
}
|
|
PythonRef args2(Py_BuildValue("(O)", settings_obj), PythonRef::kSteal);
|
|
settings = g_python->obj(Python::ObjID::kShallowCopyCall).Call(args2);
|
|
if (!settings.exists()) {
|
|
throw Exception("Unable to shallow-copy settings.");
|
|
}
|
|
} else {
|
|
settings.Acquire(settings_obj);
|
|
}
|
|
|
|
HostSession* hs = Context::current().GetHostSession();
|
|
if (!hs) {
|
|
throw Exception("No HostSession found.", PyExcType::kContext);
|
|
}
|
|
return hs->NewHostActivity(activity_type_obj, settings.get());
|
|
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyGetActivity(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("get_activity");
|
|
int raise = true;
|
|
static const char* kwlist[] = {"doraise", nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|i",
|
|
const_cast<char**>(kwlist), &raise)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Fail gracefully if called from outside the game thread.
|
|
if (!InGameThread()) {
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
if (HostActivity* hostactivity = Context::current().GetHostActivity()) {
|
|
PyObject* obj = hostactivity->GetPyActivity();
|
|
Py_INCREF(obj);
|
|
return obj;
|
|
} else {
|
|
if (raise) {
|
|
throw Exception(PyExcType::kActivityNotFound);
|
|
}
|
|
}
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyPushCall(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("push_call");
|
|
PyObject* call_obj;
|
|
int from_other_thread{};
|
|
int suppress_warning{};
|
|
static const char* kwlist[] = {"call", "from_other_thread",
|
|
"suppress_other_thread_warning", nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|ip",
|
|
const_cast<char**>(kwlist), &call_obj,
|
|
&from_other_thread, &suppress_warning)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// The from-other-thread case is basically a different call.
|
|
if (from_other_thread) {
|
|
// Warn the user not to use this from the game thread since it doesnt
|
|
// save/restore context.
|
|
if (!suppress_warning && InGameThread()) {
|
|
g_python->IssueCallInGameThreadWarning(call_obj);
|
|
}
|
|
|
|
// This gets called from other python threads so we can't construct
|
|
// Objects and things here or we'll trip our thread-checks. Instead we
|
|
// just increment the python object's refcount and pass it along raw;
|
|
// the game thread decrements it on the other end.
|
|
Py_INCREF(call_obj);
|
|
g_game->PushPythonRawCallable(call_obj);
|
|
} else {
|
|
if (!InGameThread()) {
|
|
throw Exception("You must use from_other_thread mode.");
|
|
}
|
|
g_game->PushPythonCall(Object::New<PythonContextCall>(call_obj));
|
|
}
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyTime(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("time");
|
|
|
|
PyObject* time_type_obj = nullptr;
|
|
PyObject* time_format_obj = nullptr;
|
|
static const char* kwlist[] = {"timetype", "timeformat", nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|OO",
|
|
const_cast<char**>(kwlist), &time_type_obj,
|
|
&time_format_obj)) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto time_type = TimeType::kSim;
|
|
if (time_type_obj != nullptr) {
|
|
time_type = Python::GetPyEnum_TimeType(time_type_obj);
|
|
}
|
|
auto time_format = TimeFormat::kSeconds;
|
|
if (time_format_obj != nullptr) {
|
|
time_format = Python::GetPyEnum_TimeFormat(time_format_obj);
|
|
}
|
|
|
|
millisecs_t timeval;
|
|
if (time_type == TimeType::kReal) {
|
|
// Special case; we don't require a context for 'real'.
|
|
timeval = GetRealTime();
|
|
} else {
|
|
// Make sure we've got a valid context-target and ask it for
|
|
// this type of time.
|
|
if (!Context::current().target.exists()) {
|
|
throw Exception(PyExcType::kContext);
|
|
}
|
|
timeval = Context::current().target->GetTime(time_type);
|
|
}
|
|
|
|
if (time_format == TimeFormat::kSeconds) {
|
|
return PyFloat_FromDouble(0.001 * timeval);
|
|
} else if (time_format == TimeFormat::kMilliseconds) {
|
|
return PyLong_FromLong(static_cast_check_fit<long>(timeval)); // NOLINT
|
|
} else {
|
|
throw Exception(
|
|
"Invalid timeformat: " + std::to_string(static_cast<int>(time_format)),
|
|
PyExcType::kValue);
|
|
}
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyTimer(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
assert(InGameThread());
|
|
Platform::SetLastPyCall("timer");
|
|
|
|
PyObject* length_obj;
|
|
int64_t length;
|
|
int repeat = 0;
|
|
int suppress_format_warning = 0;
|
|
PyObject* call_obj;
|
|
PyObject* time_type_obj = nullptr;
|
|
PyObject* time_format_obj = nullptr;
|
|
static const char* kwlist[] = {"time", "call",
|
|
"repeat", "timetype",
|
|
"timeformat", "suppress_format_warning",
|
|
nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(
|
|
args, keywds, "OO|pOOp", const_cast<char**>(kwlist), &length_obj,
|
|
&call_obj, &repeat, &time_type_obj, &time_format_obj,
|
|
&suppress_format_warning)) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto time_type = TimeType::kSim;
|
|
if (time_type_obj != nullptr) {
|
|
time_type = Python::GetPyEnum_TimeType(time_type_obj);
|
|
}
|
|
auto time_format = TimeFormat::kSeconds;
|
|
if (time_format_obj != nullptr) {
|
|
time_format = Python::GetPyEnum_TimeFormat(time_format_obj);
|
|
}
|
|
|
|
#if BA_TEST_BUILD || BA_DEBUG_BUILD
|
|
if (!suppress_format_warning) {
|
|
g_python->TimeFormatCheck(time_format, length_obj);
|
|
}
|
|
#endif
|
|
|
|
// We currently work with integer milliseconds internally.
|
|
if (time_format == TimeFormat::kSeconds) {
|
|
length = static_cast<int>(Python::GetPyDouble(length_obj) * 1000.0);
|
|
} else if (time_format == TimeFormat::kMilliseconds) {
|
|
length = Python::GetPyInt64(length_obj);
|
|
} else {
|
|
throw Exception("invalid timeformat: '"
|
|
+ std::to_string(static_cast<int>(time_format)) + "'",
|
|
PyExcType::kValue);
|
|
}
|
|
if (length < 0) {
|
|
throw Exception("Timer length < 0", PyExcType::kValue);
|
|
}
|
|
|
|
// Grab a ref to this here so it doesn't leak on exceptions.
|
|
auto runnable(Object::New<Runnable, PythonContextCallRunnable>(call_obj));
|
|
|
|
// Special case; we disallow repeating real timers currently.
|
|
if (time_type == TimeType::kReal && repeat) {
|
|
throw Exception("Repeating real timers not allowed here; use ba.Timer().",
|
|
PyExcType::kValue);
|
|
}
|
|
|
|
// Now just make sure we've got a valid context-target and ask us to
|
|
// make us a timer.
|
|
if (!Context::current().target.exists()) {
|
|
throw Exception(PyExcType::kContext);
|
|
}
|
|
Context::current().target->NewTimer(time_type, length,
|
|
static_cast<bool>(repeat), runnable);
|
|
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyScreenMessage(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("screen_message");
|
|
const char* message = nullptr;
|
|
PyObject* color_obj = Py_None;
|
|
int top = 0;
|
|
int transient = 0;
|
|
PyObject* image_obj = Py_None;
|
|
PyObject* message_obj;
|
|
PyObject* clients_obj = Py_None;
|
|
int log = 0;
|
|
static const char* kwlist[] = {"message", "color", "top", "image",
|
|
"log", "clients", "transient", nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(
|
|
args, keywds, "O|OpOiOi", const_cast<char**>(kwlist), &message_obj,
|
|
&color_obj, &top, &image_obj, &log, &clients_obj, &transient)) {
|
|
return nullptr;
|
|
}
|
|
std::string message_str = Python::GetPyString(message_obj);
|
|
message = message_str.c_str();
|
|
Vector3f color{1, 1, 1};
|
|
if (color_obj != Py_None) {
|
|
color = Python::GetPyVector3f(color_obj);
|
|
}
|
|
if (message == nullptr) {
|
|
PyErr_SetString(PyExc_AttributeError, "No message provided");
|
|
return nullptr;
|
|
}
|
|
if (log) {
|
|
Log(message);
|
|
}
|
|
|
|
// Transient messages get sent to clients as high-level messages instead of
|
|
// being embedded into the game-stream.
|
|
if (transient) {
|
|
// This option doesn't support top or icons currently.
|
|
if (image_obj != Py_None) {
|
|
throw Exception(
|
|
"The 'image' option is not currently supported for transient mode "
|
|
"messages.",
|
|
PyExcType::kValue);
|
|
}
|
|
if (top) {
|
|
throw Exception(
|
|
"The 'top' option is not currently supported for transient mode "
|
|
"messages.",
|
|
PyExcType::kValue);
|
|
}
|
|
std::vector<int32_t> client_ids;
|
|
if (clients_obj != Py_None) {
|
|
std::vector<int> client_ids2 = Python::GetPyInts(clients_obj);
|
|
g_game->connections()->SendScreenMessageToSpecificClients(
|
|
message, color.x, color.y, color.z, client_ids2);
|
|
} else {
|
|
g_game->connections()->SendScreenMessageToAll(message, color.x, color.y,
|
|
color.z);
|
|
}
|
|
} else {
|
|
// Currently specifying client_ids only works for transient messages; we'd
|
|
// need a protocol change to support that in game output streams.
|
|
// (or maintaining separate streams per client; yuck)
|
|
if (clients_obj != Py_None) {
|
|
throw Exception(
|
|
"Specifying clients only works when using the 'transient' option",
|
|
PyExcType::kValue);
|
|
}
|
|
Scene* context_scene = Context::current().GetMutableScene();
|
|
GameStream* output_stream =
|
|
context_scene ? context_scene->GetGameStream() : nullptr;
|
|
|
|
Texture* texture = nullptr;
|
|
Texture* tint_texture = nullptr;
|
|
Vector3f tint_color{1.0f, 1.0f, 1.0f};
|
|
Vector3f tint2_color{1.0f, 1.0f, 1.0f};
|
|
if (image_obj != Py_None) {
|
|
if (PyDict_Check(image_obj)) {
|
|
PyObject* obj = PyDict_GetItemString(image_obj, "texture");
|
|
if (!obj)
|
|
throw Exception("Provided image dict contains no 'texture' entry.",
|
|
PyExcType::kValue);
|
|
texture = Python::GetPyTexture(obj);
|
|
|
|
obj = PyDict_GetItemString(image_obj, "tint_texture");
|
|
if (!obj)
|
|
throw Exception(
|
|
"Provided image dict contains no 'tint_texture' entry.",
|
|
PyExcType::kValue);
|
|
tint_texture = Python::GetPyTexture(obj);
|
|
|
|
obj = PyDict_GetItemString(image_obj, "tint_color");
|
|
if (!obj)
|
|
throw Exception("Provided image dict contains no 'tint_color' entry",
|
|
PyExcType::kValue);
|
|
tint_color = Python::GetPyVector3f(obj);
|
|
obj = PyDict_GetItemString(image_obj, "tint2_color");
|
|
if (!obj)
|
|
throw Exception("Provided image dict contains no 'tint2_color' entry",
|
|
PyExcType::kValue);
|
|
tint2_color = Python::GetPyVector3f(obj);
|
|
} else {
|
|
texture = Python::GetPyTexture(image_obj);
|
|
}
|
|
}
|
|
if (output_stream) {
|
|
// FIXME: for now we just do bottom messages.
|
|
if (texture == nullptr && !top) {
|
|
output_stream->ScreenMessageBottom(message, color.x, color.y, color.z);
|
|
} else if (top && texture != nullptr && tint_texture != nullptr) {
|
|
if (texture->scene() != context_scene) {
|
|
throw Exception("Texture is not from the current context.",
|
|
PyExcType::kContext);
|
|
}
|
|
if (tint_texture->scene() != context_scene)
|
|
throw Exception("Tint-texture is not from the current context.",
|
|
PyExcType::kContext);
|
|
output_stream->ScreenMessageTop(
|
|
message, color.x, color.y, color.z, texture, tint_texture,
|
|
tint_color.x, tint_color.y, tint_color.z, tint2_color.x,
|
|
tint2_color.y, tint2_color.z);
|
|
} else {
|
|
Log("Error: unhandled screenmessage output_stream case");
|
|
}
|
|
}
|
|
|
|
// Now display it locally.
|
|
g_graphics->AddScreenMessage(message, color, static_cast<bool>(top),
|
|
texture, tint_texture, tint_color,
|
|
tint2_color);
|
|
}
|
|
|
|
Py_RETURN_NONE;
|
|
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyQuit(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("quit");
|
|
static const char* kwlist[] = {"soft", "back", nullptr};
|
|
int soft = 0;
|
|
int back = 0;
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|ii",
|
|
const_cast<char**>(kwlist), &soft, &back)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// FIXME this should all just go through platform
|
|
|
|
if (g_buildconfig.ostype_ios_tvos()) {
|
|
// This should never be called on iOS
|
|
Log("Error: Quit called.");
|
|
}
|
|
|
|
bool handled = false;
|
|
|
|
// A few types get handled specially on android.
|
|
if (g_buildconfig.ostype_android()) {
|
|
#pragma clang diagnostic push
|
|
#pragma ide diagnostic ignored "ConstantConditionsOC"
|
|
|
|
if (!handled && back) {
|
|
// Back-quit simply synthesizes a back press.
|
|
// Note to self: I remember this behaved slightly differently than
|
|
// doing a soft quit but I should remind myself how...
|
|
g_platform->AndroidSynthesizeBackPress();
|
|
handled = true;
|
|
}
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
if (!handled && soft) {
|
|
// Soft-quit just kills our activity but doesn't run app shutdown.
|
|
// Thus we'll be able to spin back up (reset to the main menu)
|
|
// if the user re-launches us.
|
|
g_platform->AndroidQuitActivity();
|
|
handled = true;
|
|
}
|
|
}
|
|
if (!handled) {
|
|
g_game->PushShutdownCall(false);
|
|
}
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
#if BA_DEBUG_BUILD
|
|
auto PyBless(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("bless");
|
|
ScreenMessage("WOULD BLESS BUILD " + std::to_string(kAppBuildNumber));
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
#endif // BA_DEBUG_BUILD
|
|
|
|
auto PyApplyConfig(PyObject* self, PyObject* args) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("apply_config");
|
|
|
|
// Hmm; python runs in the game thread; technically we could just run
|
|
// ApplyConfig() immediately (though pushing is probably safer).
|
|
g_game->PushApplyConfigCall();
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyCommitConfig(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("commit_config");
|
|
PyObject* config_obj;
|
|
static const char* kwlist[] = {"config", nullptr};
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O",
|
|
const_cast<char**>(kwlist), &config_obj)) {
|
|
return nullptr;
|
|
}
|
|
if (config_obj == nullptr || !Python::IsPyString(config_obj)) {
|
|
throw Exception("ERROR ON JSON DUMP");
|
|
}
|
|
std::string final_str = Python::GetPyString(config_obj);
|
|
std::string path = g_platform->GetConfigFilePath();
|
|
std::string path_temp = path + ".tmp";
|
|
std::string path_prev = path + ".prev";
|
|
if (explicit_bool(true)) {
|
|
FILE* f_out = g_platform->FOpen(path_temp.c_str(), "wb");
|
|
if (f_out == nullptr) {
|
|
throw Exception("Error opening config file for writing: '" + path_temp
|
|
+ "': " + g_platform->GetErrnoString());
|
|
}
|
|
|
|
// Write to temp file.
|
|
size_t result = fwrite(&final_str[0], final_str.size(), 1, f_out);
|
|
if (result != 1) {
|
|
fclose(f_out);
|
|
throw Exception("Error writing config file to '" + path_temp
|
|
+ "': " + g_platform->GetErrnoString());
|
|
}
|
|
fclose(f_out);
|
|
|
|
// Now backup any existing config to .prev.
|
|
if (g_platform->FilePathExists(path)) {
|
|
// On windows, rename doesn't overwrite existing files.. need to kill
|
|
// the old explicitly.
|
|
// (hmm; should we just do this everywhere for consistency?)
|
|
if (g_buildconfig.ostype_windows()) {
|
|
if (g_platform->FilePathExists(path_prev)) {
|
|
int result2 = g_platform->Remove(path_prev.c_str());
|
|
if (result2 != 0) {
|
|
throw Exception("Error removing prev config file '" + path_prev
|
|
+ "': " + g_platform->GetErrnoString());
|
|
}
|
|
}
|
|
}
|
|
int result2 = g_platform->Rename(path.c_str(), path_prev.c_str());
|
|
if (result2 != 0) {
|
|
throw Exception("Error backing up config file to '" + path_prev
|
|
+ "': " + g_platform->GetErrnoString());
|
|
}
|
|
}
|
|
|
|
// Now move temp into place.
|
|
int result2 = g_platform->Rename(path_temp.c_str(), path.c_str());
|
|
if (result2 != 0) {
|
|
throw Exception("Error renaming temp config file to final '" + path
|
|
+ "': " + g_platform->GetErrnoString());
|
|
}
|
|
}
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyEnv(PyObject* self) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("env");
|
|
|
|
static PyObject* env_obj = nullptr;
|
|
|
|
// Just build this once and recycle it.
|
|
if (env_obj == nullptr) {
|
|
std::string config_path = g_platform->GetConfigFilePath();
|
|
PyObject* is_debug_build_obj;
|
|
#if BA_DEBUG_BUILD
|
|
is_debug_build_obj = Py_True;
|
|
#else
|
|
is_debug_build_obj = Py_False;
|
|
#endif
|
|
PyObject* is_test_build_obj;
|
|
#if BA_TEST_BUILD
|
|
is_test_build_obj = Py_True;
|
|
#else
|
|
is_test_build_obj = Py_False;
|
|
#endif
|
|
bool demo_mode{g_buildconfig.demo_build()};
|
|
|
|
const char* ui_scale;
|
|
switch (GetUIScale()) {
|
|
case UIScale::kLarge:
|
|
ui_scale = "large";
|
|
break;
|
|
case UIScale::kMedium:
|
|
ui_scale = "medium";
|
|
break;
|
|
case UIScale::kSmall:
|
|
ui_scale = "small";
|
|
break;
|
|
default:
|
|
throw Exception();
|
|
}
|
|
// clang-format off
|
|
env_obj = Py_BuildValue(
|
|
"{"
|
|
"si" // build_number
|
|
"ss" // config_file_path
|
|
"ss" // locale
|
|
"ss" // user_agent_string
|
|
"ss" // version
|
|
"sO" // debug_build
|
|
"sO" // test_build
|
|
"ss" // python_directory_user
|
|
"ss" // python_directory_app
|
|
"ss" // platform
|
|
"ss" // subplatform
|
|
"ss" // ui_scale
|
|
"sO" // on_tv
|
|
"sO" // vr_mode
|
|
"sO" // toolbar_test
|
|
"sO" // demo_mode
|
|
"sO" // arcade_mode
|
|
"sO" // iircade_mode
|
|
"si" // protocol_version
|
|
"sO" // headless_mode
|
|
"ss" // python_directory_app_site
|
|
"}",
|
|
"build_number", kAppBuildNumber,
|
|
"config_file_path", config_path.c_str(),
|
|
"locale", g_platform->GetLocale().c_str(),
|
|
"user_agent_string", g_app_globals->user_agent_string.c_str(),
|
|
"version", kAppVersion,
|
|
"debug_build", is_debug_build_obj,
|
|
"test_build", is_test_build_obj,
|
|
"python_directory_user", g_platform->GetUserPythonDirectory().c_str(),
|
|
"python_directory_app", g_platform->GetAppPythonDirectory().c_str(),
|
|
"platform", g_platform->GetPlatformName().c_str(),
|
|
"subplatform", g_platform->GetSubplatformName().c_str(),
|
|
"ui_scale", ui_scale,
|
|
"on_tv", g_platform->IsRunningOnTV() ? Py_True : Py_False,
|
|
"vr_mode", IsVRMode() ? Py_True : Py_False,
|
|
"toolbar_test", BA_TOOLBAR_TEST ? Py_True : Py_False,
|
|
"demo_mode", demo_mode ? Py_True : Py_False,
|
|
"arcade_mode", g_buildconfig.arcade_build() ? Py_True : Py_False,
|
|
"iircade_mode", g_buildconfig.iircade_build() ? Py_True: Py_False,
|
|
"protocol_version", kProtocolVersion,
|
|
"headless_mode", HeadlessMode() ? Py_True : Py_False,
|
|
"python_directory_app_site",
|
|
g_platform->GetSitePythonDirectory().c_str());
|
|
// clang-format on
|
|
}
|
|
Py_INCREF(env_obj);
|
|
g_python->set_env_obj(env_obj);
|
|
return env_obj;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PySetStressTesting(PyObject* self, PyObject* args) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("set_stress_testing");
|
|
int testing;
|
|
int player_count;
|
|
if (!PyArg_ParseTuple(args, "pi", &testing, &player_count)) {
|
|
return nullptr;
|
|
}
|
|
g_app->PushSetStressTestingCall(static_cast<bool>(testing), player_count);
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyPrintStdout(PyObject* self, PyObject* args) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("print_stdout");
|
|
const char* s;
|
|
if (!PyArg_ParseTuple(args, "s", &s)) {
|
|
return nullptr;
|
|
}
|
|
Logging::PrintStdout(s);
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyPrintStderr(PyObject* self, PyObject* args) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("print_stderr");
|
|
const char* s;
|
|
if (!PyArg_ParseTuple(args, "s", &s)) {
|
|
return nullptr;
|
|
}
|
|
Logging::PrintStderr(s);
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyLog(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("log");
|
|
static const char* kwlist[] = {"message", "to_stdout", "to_server", nullptr};
|
|
int to_server = 1;
|
|
int to_stdout = 1;
|
|
std::string message;
|
|
PyObject* message_obj;
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|pp",
|
|
const_cast<char**>(kwlist), &message_obj,
|
|
&to_stdout, &to_server)) {
|
|
return nullptr;
|
|
}
|
|
message = Python::GetPyString(message_obj);
|
|
Log(message, static_cast<bool>(to_stdout), static_cast<bool>(to_server));
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PyTimeFormatCheck(PyObject* self, PyObject* args, PyObject* keywds)
|
|
-> PyObject* {
|
|
BA_PYTHON_TRY;
|
|
Platform::SetLastPyCall("time_format_check");
|
|
static const char* kwlist[] = {"time_format", "length", nullptr};
|
|
PyObject* time_format_obj;
|
|
PyObject* length_obj;
|
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "OO",
|
|
const_cast<char**>(kwlist), &time_format_obj,
|
|
&length_obj)) {
|
|
return nullptr;
|
|
}
|
|
auto time_format = Python::GetPyEnum_TimeFormat(time_format_obj);
|
|
|
|
g_python->TimeFormatCheck(time_format, length_obj);
|
|
Py_RETURN_NONE;
|
|
BA_PYTHON_CATCH;
|
|
}
|
|
|
|
auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
|
return {
|
|
{"appname", (PyCFunction)PyAppName, METH_NOARGS,
|
|
"appname() -> str\n"
|
|
"\n"
|
|
"(internal)\n"},
|
|
{"appnameupper", (PyCFunction)PyAppNameUpper, METH_NOARGS,
|
|
"appnameupper() -> str\n"
|
|
"\n"
|
|
"(internal)\n"
|
|
"\n"
|
|
"Return whether this build of the game can display full unicode such "
|
|
"as\n"
|
|
"Emoji, Asian languages, etc.\n"},
|
|
{"is_xcode_build", (PyCFunction)PyIsXCodeBuild, METH_NOARGS,
|
|
"is_xcode_build() -> bool\n"
|
|
"\n"
|
|
"(internal)\n"},
|
|
{"can_display_full_unicode", (PyCFunction)PyCanDisplayFullUnicode,
|
|
METH_NOARGS,
|
|
"can_display_full_unicode() -> bool\n"
|
|
"\n"
|
|
"(internal)\n"},
|
|
{"time_format_check", (PyCFunction)PyTimeFormatCheck,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"time_format_check(time_format: ba.TimeFormat, length: Union[float, "
|
|
"int])\n"
|
|
" -> None\n"
|
|
"\n"
|
|
"(internal)\n"
|
|
"\n"
|
|
"Logs suspicious time values for timers or animate calls.\n"
|
|
"\n"
|
|
"(for helping with the transition from milliseconds-based time calls\n"
|
|
"to seconds-based ones)"},
|
|
|
|
{"log", (PyCFunction)PyLog, METH_VARARGS | METH_KEYWORDS,
|
|
"log(message: str, to_stdout: bool = True,\n"
|
|
" to_server: bool = True) -> None\n"
|
|
"\n"
|
|
"Category: General Utility Functions\n"
|
|
"\n"
|
|
"Log a message. This goes to the default logging mechanism depending\n"
|
|
"on the platform (stdout on mac, android log on android, etc).\n"
|
|
"\n"
|
|
"Log messages also go to the in-game console unless 'to_console'\n"
|
|
"is False. They are also sent to the master-server for use in "
|
|
"analyzing\n"
|
|
"issues unless to_server is False.\n"
|
|
"\n"
|
|
"Python's standard print() is wired to call this (with default "
|
|
"values)\n"
|
|
"so in most cases you can just use that."},
|
|
|
|
{"print_stdout", PyPrintStdout, METH_VARARGS,
|
|
"print_stdout(message: str) -> None\n"
|
|
"\n"
|
|
"(internal)"},
|
|
|
|
{"print_stderr", PyPrintStderr, METH_VARARGS,
|
|
"print_stderr(message: str) -> None\n"
|
|
"\n"
|
|
"(internal)"},
|
|
|
|
{"set_stress_testing", PySetStressTesting, METH_VARARGS,
|
|
"set_stress_testing(testing: bool, player_count: int) -> None\n"
|
|
"\n"
|
|
"(internal)"},
|
|
|
|
{"env", (PyCFunction)PyEnv, METH_NOARGS,
|
|
"env() -> dict\n"
|
|
"\n"
|
|
"(internal)\n"
|
|
"\n"
|
|
"Returns a dict containing general info about the operating "
|
|
"environment\n"
|
|
"such as version, platform, etc.\n"
|
|
"This info is now exposed through ba.App; refer to those docs for\n"
|
|
"info on specific elements."},
|
|
|
|
{"commit_config", (PyCFunction)PyCommitConfig,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"commit_config(config: str) -> None\n"
|
|
"\n"
|
|
"(internal)"},
|
|
|
|
{"apply_config", PyApplyConfig, METH_VARARGS,
|
|
"apply_config() -> None\n"
|
|
"\n"
|
|
"(internal)"},
|
|
|
|
#if BA_DEBUG_BUILD
|
|
{"bless", (PyCFunction)PyBless, METH_VARARGS | METH_KEYWORDS,
|
|
"bless() -> None\n"
|
|
"\n"
|
|
"(internal)"},
|
|
#endif
|
|
|
|
{"quit", (PyCFunction)PyQuit, METH_VARARGS | METH_KEYWORDS,
|
|
"quit(soft: bool = False, back: bool = False) -> None\n"
|
|
"\n"
|
|
"Quit the game.\n"
|
|
"\n"
|
|
"Category: General Utility Functions\n"
|
|
"\n"
|
|
"On systems like android, 'soft' will end the activity but keep the\n"
|
|
"app running."},
|
|
|
|
{"screenmessage", (PyCFunction)PyScreenMessage,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"screenmessage(message: Union[str, ba.Lstr],\n"
|
|
" color: Sequence[float] = None, top: bool = False,\n"
|
|
" image: Dict[str, Any] = None, log: bool = False,\n"
|
|
" clients: Sequence[int] = None, transient: bool = False) -> None\n"
|
|
"\n"
|
|
"Print a message to the local client's screen, in a given color.\n"
|
|
"\n"
|
|
"Category: General Utility Functions\n"
|
|
"\n"
|
|
"If 'top' is True, the message will go to the top message area.\n"
|
|
"For 'top' messages, 'image' can be a texture to display alongside "
|
|
"the\n"
|
|
"message.\n"
|
|
"If 'log' is True, the message will also be printed to the output "
|
|
"log\n"
|
|
"'clients' can be a list of client-ids the message should be sent "
|
|
"to,\n"
|
|
"or None to specify that everyone should receive it.\n"
|
|
"If 'transient' is True, the message will not be included in the\n"
|
|
"game-stream and thus will not show up when viewing replays.\n"
|
|
"Currently the 'clients' option only works for transient messages."},
|
|
|
|
{"timer", (PyCFunction)PyTimer, METH_VARARGS | METH_KEYWORDS,
|
|
"timer(time: float, call: Callable[[], Any], repeat: bool = False,\n"
|
|
" timetype: ba.TimeType = TimeType.SIM,\n"
|
|
" timeformat: ba.TimeFormat = TimeFormat.SECONDS,\n"
|
|
" suppress_format_warning: bool = False)\n"
|
|
" -> None\n"
|
|
"\n"
|
|
"Schedule a call to run at a later point in time.\n"
|
|
"\n"
|
|
"Category: General Utility Functions\n"
|
|
"\n"
|
|
"This function adds a timer to the current ba.Context.\n"
|
|
"This timer cannot be canceled or modified once created. If you\n"
|
|
" require the ability to do so, use the ba.Timer class instead.\n"
|
|
"\n"
|
|
"time: length of time (in seconds by default) that the timer will "
|
|
"wait\n"
|
|
"before firing. Note that the actual delay experienced may vary\n "
|
|
"depending on the timetype. (see below)\n"
|
|
"\n"
|
|
"call: A callable Python object. Note that the timer will retain a\n"
|
|
"strong reference to the callable for as long as it exists, so you\n"
|
|
"may want to look into concepts such as ba.WeakCall if that is not\n"
|
|
"desired.\n"
|
|
"\n"
|
|
"repeat: if True, the timer will fire repeatedly, with each "
|
|
"successive\n"
|
|
"firing having the same delay as the first.\n"
|
|
"\n"
|
|
"timetype can be either 'sim', 'base', or 'real'. It defaults to\n"
|
|
"'sim'. Types are explained below:\n"
|
|
"\n"
|
|
"'sim' time maps to local simulation time in ba.Activity or "
|
|
"ba.Session\n"
|
|
"Contexts. This means that it may progress slower in slow-motion "
|
|
"play\n"
|
|
"modes, stop when the game is paused, etc. This time type is not\n"
|
|
"available in UI contexts.\n"
|
|
"\n"
|
|
"'base' time is also linked to gameplay in ba.Activity or ba.Session\n"
|
|
"Contexts, but it progresses at a constant rate regardless of\n "
|
|
"slow-motion states or pausing. It can, however, slow down or stop\n"
|
|
"in certain cases such as network outages or game slowdowns due to\n"
|
|
"cpu load. Like 'sim' time, this is unavailable in UI contexts.\n"
|
|
"\n"
|
|
"'real' time always maps to actual clock time with a bit of "
|
|
"filtering\n"
|
|
"added, regardless of Context. (the filtering prevents it from "
|
|
"going\n"
|
|
"backwards or jumping forward by large amounts due to the app being\n"
|
|
"backgrounded, system time changing, etc.)\n"
|
|
"Real time timers are currently only available in the UI context.\n"
|
|
"\n"
|
|
"the 'timeformat' arg defaults to seconds but can also be "
|
|
"milliseconds.\n"
|
|
"\n"
|
|
"# timer example: print some stuff through time:\n"
|
|
"ba.screenmessage('hello from now!')\n"
|
|
"ba.timer(1.0, ba.Call(ba.screenmessage, 'hello from the future!'))\n"
|
|
"ba.timer(2.0, ba.Call(ba.screenmessage, 'hello from the future "
|
|
"2!'))\n"},
|
|
|
|
{"time", (PyCFunction)PyTime, METH_VARARGS | METH_KEYWORDS,
|
|
"time(timetype: ba.TimeType = TimeType.SIM,\n"
|
|
" timeformat: ba.TimeFormat = TimeFormat.SECONDS)\n"
|
|
" -> <varies>\n"
|
|
"\n"
|
|
"Return the current time.\n"
|
|
"\n"
|
|
"Category: General Utility Functions\n"
|
|
"\n"
|
|
"The time returned depends on the current ba.Context and timetype.\n"
|
|
"\n"
|
|
"timetype can be either SIM, BASE, or REAL. It defaults to\n"
|
|
"SIM. Types are explained below:\n"
|
|
"\n"
|
|
"SIM time maps to local simulation time in ba.Activity or ba.Session\n"
|
|
"Contexts. This means that it may progress slower in slow-motion "
|
|
"play\n"
|
|
"modes, stop when the game is paused, etc. This time type is not\n"
|
|
"available in UI contexts.\n"
|
|
"\n"
|
|
"BASE time is also linked to gameplay in ba.Activity or ba.Session\n"
|
|
"Contexts, but it progresses at a constant rate regardless of\n "
|
|
"slow-motion states or pausing. It can, however, slow down or stop\n"
|
|
"in certain cases such as network outages or game slowdowns due to\n"
|
|
"cpu load. Like 'sim' time, this is unavailable in UI contexts.\n"
|
|
"\n"
|
|
"REAL time always maps to actual clock time with a bit of filtering\n"
|
|
"added, regardless of Context. (the filtering prevents it from "
|
|
"going\n"
|
|
"backwards or jumping forward by large amounts due to the app being\n"
|
|
"backgrounded, system time changing, etc.)\n"
|
|
"\n"
|
|
"the 'timeformat' arg defaults to SECONDS which returns float "
|
|
"seconds,\n"
|
|
"but it can also be MILLISECONDS to return integer milliseconds.\n"
|
|
"\n"
|
|
"Note: If you need pure unfiltered clock time, just use the standard\n"
|
|
"Python functions such as time.time()."},
|
|
|
|
{"pushcall", (PyCFunction)PyPushCall, METH_VARARGS | METH_KEYWORDS,
|
|
"pushcall(call: Callable, from_other_thread: bool = False,\n"
|
|
" suppress_other_thread_warning: bool = False ) -> None\n"
|
|
"\n"
|
|
"Pushes a call onto the event loop to be run during the next cycle.\n"
|
|
"\n"
|
|
"Category: General Utility Functions\n"
|
|
"\n"
|
|
"This can be handy for calls that are disallowed from within other\n"
|
|
"callbacks, etc.\n"
|
|
"\n"
|
|
"This call expects to be used in the game thread, and will "
|
|
"automatically\n"
|
|
"save and restore the ba.Context to behave seamlessly.\n"
|
|
"\n"
|
|
"If you want to push a call from outside of the game thread,\n"
|
|
"however, you can pass 'from_other_thread' as True. In this case\n"
|
|
"the call will always run in the UI context on the game thread."},
|
|
|
|
{"getactivity", (PyCFunction)PyGetActivity,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"getactivity(doraise: bool = True) -> <varies>\n"
|
|
"\n"
|
|
"Return the current ba.Activity instance.\n"
|
|
"\n"
|
|
"Category: Gameplay Functions\n"
|
|
"\n"
|
|
"Note that this is based on context; thus code run in a timer "
|
|
"generated\n"
|
|
"in Activity 'foo' will properly return 'foo' here, even if another\n"
|
|
"Activity has since been created or is transitioning in.\n"
|
|
"If there is no current Activity, raises a ba.ActivityNotFoundError.\n"
|
|
"If doraise is False, None will be returned instead in that case."},
|
|
|
|
{"newactivity", (PyCFunction)PyNewActivity,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"newactivity(activity_type: Type[ba.Activity],\n"
|
|
" settings: dict = None) -> ba.Activity\n"
|
|
"\n"
|
|
"Instantiates a ba.Activity given a type object.\n"
|
|
"\n"
|
|
"Category: General Utility Functions\n"
|
|
"\n"
|
|
"Activities require special setup and thus cannot be directly\n"
|
|
"instantiated; you must go through this function."},
|
|
|
|
{"get_foreground_host_session", (PyCFunction)PyGetForegroundHostSession,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"get_foreground_host_session() -> Optional[ba.Session]\n"
|
|
"\n"
|
|
"(internal)\n"
|
|
"\n"
|
|
"Return the ba.Session currently being displayed, or None if there "
|
|
"is\n"
|
|
"none."},
|
|
|
|
{"register_activity", (PyCFunction)PyRegisterActivity,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"register_activity(activity: ba.Activity) -> ActivityData\n"
|
|
"\n"
|
|
"(internal)"},
|
|
|
|
{"register_session", (PyCFunction)PyRegisterSession,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"register_session(session: ba.Session) -> SessionData\n"
|
|
"\n"
|
|
"(internal)"},
|
|
|
|
{"is_in_replay", (PyCFunction)PyIsInReplay,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"is_in_replay() -> bool\n"
|
|
"\n"
|
|
"(internal)"},
|
|
|
|
{"new_replay_session", (PyCFunction)PyNewReplaySession,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"new_replay_session(file_name: str) -> None\n"
|
|
"\n"
|
|
"(internal)"},
|
|
|
|
{"new_host_session", (PyCFunction)PyNewHostSession,
|
|
METH_VARARGS | METH_KEYWORDS,
|
|
"new_host_session(sessiontype: Type[ba.Session],\n"
|
|
" benchmark_type: str = None) -> None\n"
|
|
"\n"
|
|
"(internal)"},
|
|
|
|
{"getsession", (PyCFunction)PyGetSession, METH_VARARGS | METH_KEYWORDS,
|
|
"getsession(doraise: bool = True) -> <varies>\n"
|
|
"\n"
|
|
"Category: Gameplay Functions\n"
|
|
"\n"
|
|
"Returns the current ba.Session instance.\n"
|
|
"Note that this is based on context; thus code being run in the UI\n"
|
|
"context will return the UI context here even if a game Session also\n"
|
|
"exists, etc. If there is no current Session, an Exception is raised, "
|
|
"or\n"
|
|
"if doraise is False then None is returned instead."},
|
|
};
|
|
}
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
} // namespace ballistica
|