diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index 2a38dd721c57..0a5da07c6430 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -157,6 +157,7 @@ + diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 76793bd9ae4d..c8c43777ac57 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -274,6 +274,7 @@ Debug + diff --git a/Source/Core/Common/TupleUtil.h b/Source/Core/Common/TupleUtil.h new file mode 100644 index 000000000000..9a7139813a4f --- /dev/null +++ b/Source/Core/Common/TupleUtil.h @@ -0,0 +1,36 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +template +auto _TupleMapImpl(Tuple& t, F f, std::index_sequence) +{ + return std::make_tuple(f(std::get(t))...); +} + +// returns a new tuple with each tuple item mapped by the given function. +// The tuple is passed by reference, so the mapping function may take references to the values. +template +auto TupleMap(std::tuple& t, F f) +{ + return _TupleMapImpl(t, f, std::make_index_sequence{}); +} + + +template +static auto _TupleToArrayImpl(Tuple& t, std::index_sequence) +{ + return std::array, sizeof...(Is)>{std::get(t)...}; +} + +// turns a tuple consisting of 1 or more elements of the same type into a std::array +template +static auto TupleToArray(std::tuple& t) +{ + return _TupleToArrayImpl(t, std::make_index_sequence{}); +} + diff --git a/Source/Core/DolphinQt2/Main.cpp b/Source/Core/DolphinQt2/Main.cpp index c98b5c99c936..3fb684bfc177 100644 --- a/Source/Core/DolphinQt2/Main.cpp +++ b/Source/Core/DolphinQt2/Main.cpp @@ -31,6 +31,7 @@ #include "DolphinQt2/Updater.h" #include "UICommon/CommandLineParse.h" #include "UICommon/UICommon.h" +#include "Scripting/ScriptingEngine.h" static bool QtMsgAlertHandler(const char* caption, const char* text, bool yes_no, MsgType style) { @@ -135,6 +136,7 @@ int main(int argc, char* argv[]) UICommon::Init(); Resources::Init(); Settings::Instance().SetBatchModeEnabled(options.is_set("batch")); + Scripting::Init(); // Hook up alerts from core RegisterMsgAlertHandler(QtMsgAlertHandler); @@ -216,6 +218,7 @@ int main(int argc, char* argv[]) retval = app.exec(); } + Scripting::Shutdown(); Core::Shutdown(); UICommon::Shutdown(); Host::GetInstance()->deleteLater(); diff --git a/Source/Core/Scripting/Python/PyFramework/as_py_function.h b/Source/Core/Scripting/Python/PyFramework/as_py_function.h new file mode 100644 index 000000000000..c327c60b1396 --- /dev/null +++ b/Source/Core/Scripting/Python/PyFramework/as_py_function.h @@ -0,0 +1,64 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "Common/TupleUtil.h" + +#include "Scripting/Python/PyFramework/fmt.h" + + +namespace Py +{ + + +template +using Fun = TRet (*)(TsArgs...); + +template +struct WrapHelper; + +template TFun> +struct WrapHelper, TFun> +{ + static PyObject* func(PyObject* self, PyObject* args) + { + std::tuple args_tpl; + + std::tuple py_args_and_fmt = + std::make_tuple(args, Py::fmts.c_str()); + std::tuple args_pointers = + TupleMap(args_tpl, [](const auto& obj) { return &obj; }); + auto invoke_args = std::tuple_cat(py_args_and_fmt, args_pointers); + if (!std::apply(PyArg_ParseTuple, invoke_args)) + return nullptr; + + if constexpr (std::is_same_v) + { + std::apply(TFun, args_tpl); + Py_RETURN_NONE; + } + else + { + TRet result = std::apply(TFun, args_tpl); + return Py_BuildValue(Py::fmt, result); + } + + } +}; + +template +struct Wrap : WrapHelper +{ +}; + +template +static PyCFunction as_py_function = Wrap::func; + + +} // namespace Py diff --git a/Source/Core/Scripting/Python/PyFramework/fmt.h b/Source/Core/Scripting/Python/PyFramework/fmt.h new file mode 100644 index 000000000000..386ffe03f649 --- /dev/null +++ b/Source/Core/Scripting/Python/PyFramework/fmt.h @@ -0,0 +1,115 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + + +namespace Py +{ + + +// translates types to format strings according to the documentation: +// https://docs.python.org/3/c-api/arg.html +template +constexpr const char* GetPyFmt() +{ + static_assert(sizeof(T) != sizeof(T), "no python format string known for given type"); + return nullptr; +} +#pragma region template specializations for each type +template <> +constexpr const char* GetPyFmt() +{ + return "b"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "h"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "i"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "l"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "L"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "B"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "H"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "I"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "k"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "K"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "f"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "d"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "O"; +} +template <> +constexpr const char* GetPyFmt() +{ + return "s"; +} +#pragma endregion + +template +constexpr const char* fmt = GetPyFmt(); + +template +std::string GetPyFmts() +{ + return std::string{fmt}; +} + +template +std::string GetPyFmts() +{ + return fmt + GetPyFmts(); +} + +template +const std::string fmts = GetPyFmts(); + + +} // namespace Py diff --git a/Source/Core/Scripting/Python/PyFramework/module.h b/Source/Core/Scripting/Python/PyFramework/module.h new file mode 100644 index 000000000000..1af137f83092 --- /dev/null +++ b/Source/Core/Scripting/Python/PyFramework/module.h @@ -0,0 +1,48 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include "Scripting/Python/PyFramework/as_py_function.h" + +namespace Py +{ + + +/*template +struct FuncDef +{ + char* func_name; + T func; +};*/ + +template +constexpr PyMethodDef MakeMethodDef(const char* name) +{ + const auto x = as_py_function; + return {name, x, METH_VARARGS, ""}; +} + +/*constexpr PyModuleDef MakeModule(const char* module_name, PyMethodDef func_defs[]) +{ + return {PyModuleDef_HEAD_INIT, module_name, nullptr, -1, func_defs}; +}*/ + +/*template +PyModuleDef MakeModule(const char* module_name, FuncDef... func_defs) +{ + std::tuple func_tpl{func_defs...}; + + auto py_funcs_tpl = TupleMap(func_tpl, [](auto& x) { + return {x.func_name, as_py_function, METH_VARARGS, ""}; + }); + std::array py_funcs = TupleToArray(py_funcs_tpl); + + PyMethodDef Methods[] = {py_funcs}; + return {PyModuleDef_HEAD_INIT, module_name, nullptr, -1, Methods}; +}*/ + + +} // namespace Py diff --git a/Source/Core/Scripting/Python/PyScriptingBackend.cpp b/Source/Core/Scripting/Python/PyScriptingBackend.cpp new file mode 100644 index 000000000000..47a6e0c62b9d --- /dev/null +++ b/Source/Core/Scripting/Python/PyScriptingBackend.cpp @@ -0,0 +1,94 @@ + +#include +#include +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Core/API/Events.h" +#include "Scripting/Python/PyScriptingBackend.h" +#include "Scripting/Python/Pyr.h" +#include "Scripting/Python/memorymodule.h" +#include "DiscIO/Filesystem.h" +#include "aeventmodule.h" +#include "doliomodule.h" +#include "eventmodule.h" + +namespace PyScripting +{ + +static const std::wstring python_home = UTF8ToUTF16(File::GetExeDirectory()) + L"/python36-embed"; +static const std::wstring python_path = + UTF8ToUTF16(File::GetExeDirectory()) + L"/python-embed/python36.zip;" + // TODO make this version-robust + UTF8ToUTF16(File::GetExeDirectory()) + L"/python-embed;" + + UTF8ToUTF16(File::GetExeDirectory()); // TODO can this be done more elegantly? + +static std::deque s_tasks; +static CoreTiming::EventType* s_scripting_event = nullptr; + +API::ListenerID listener_frameadvance; +API::ListenerID listener_memory; +API::ListenerID listener_interrupt; + +static void ScriptingEvent(u64, s64) +{ + while (!s_tasks.empty()) + { + const Task task = s_tasks.front(); + s_tasks.pop_front(); + task(); + } +} + +void Init() +{ + listener_frameadvance = API::GetEventHub().ListenEvent(OnFrameAdvance); + listener_memory = API::GetEventHub().ListenEvent(OnMemory); + listener_interrupt = API::GetEventHub().ListenEvent(OnInterrupt); + + s_scripting_event = CoreTiming::RegisterEvent("", ScriptingEvent); + + PyImport_AppendInittab("dolio_stdout", PyInit_dolio_stdout); + PyImport_AppendInittab("dolio_stderr", PyInit_dolio_stderr); + PyImport_AppendInittab("dolphin_memory", PyInit_memory); + PyImport_AppendInittab("dolphin_event", PyInit_event); + PyImport_AppendInittab("dolphin_aevent", PyInit_aevent); + + Py_SetPythonHome(const_cast(python_home.c_str())); + Py_SetPath(python_path.c_str()); + Py_InitializeEx(0); + auto result_stdout = PyrImport::ImportModule("dolio_stdout"); + if (result_stdout.IsNull()) + ERROR_LOG(SCRIPTING, "Error auto-importing dolphin io module for stdout"); + auto result_stderr = PyrImport::ImportModule("dolio_stderr"); + if (result_stderr.IsNull()) + ERROR_LOG(SCRIPTING, "Error auto-importing dolphin io module for stderr"); + + // TODO check file exists + std::string filepath = File::GetExeDirectory() + "/testscript.py"; + const int result = + PyRun_SimpleFileExFlags(fopen(filepath.c_str(), "r"), filepath.c_str(), true, nullptr); + if (result != 0) + ERROR_LOG(SCRIPTING, "Error during initialization of script."); +} + +void Shutdown() +{ + API::GetEventHub().UnlistenEvent(listener_frameadvance); + API::GetEventHub().UnlistenEvent(listener_memory); + API::GetEventHub().UnlistenEvent(listener_interrupt); + + CoreTiming::RemoveEvent(s_scripting_event); + s_scripting_event = nullptr; + + PyGILState_Ensure(); + if (Py_FinalizeEx() != 0) + ERROR_LOG(SCRIPTING, "Error during python finalization: %s", PyrErr::GetException()); +} + +void RunFromCPUThreadAsync(Task task) +{ + s_tasks.push_back(task); + CoreTiming::ScheduleEvent(0, s_scripting_event, 0, CoreTiming::FromThread::NON_CPU); +} + +} // namespace PyScripting diff --git a/Source/Core/Scripting/Python/PyScriptingBackend.h b/Source/Core/Scripting/Python/PyScriptingBackend.h new file mode 100644 index 000000000000..243fc5a16faa --- /dev/null +++ b/Source/Core/Scripting/Python/PyScriptingBackend.h @@ -0,0 +1,13 @@ +#pragma once +#include + +namespace PyScripting +{ + +void Init(); +void Shutdown(); + +using Task = std::function; +void RunFromCPUThreadAsync(Task task); + +} // namespace PyScripting diff --git a/Source/Core/Scripting/Python/Pyr.h b/Source/Core/Scripting/Python/Pyr.h new file mode 100644 index 000000000000..7a1028e61dfe --- /dev/null +++ b/Source/Core/Scripting/Python/Pyr.h @@ -0,0 +1,142 @@ +#pragma once + +#include +#include "PyFramework/fmt.h" + +namespace Pyr +{ +class Object +{ + PyObject* m_py_object_; + Object(PyObject* py_object) : m_py_object_(py_object) {} + +public: + Object& operator=(Object&&) = default; + Object& operator=(const Object& rhs) + { + Py_XINCREF(rhs.Unwrap()); + Py_XDECREF(m_py_object_); + m_py_object_ = rhs.Unwrap(); + return *this; + } + Object(Object&&) = default; + Object(Object& rhs) + { + m_py_object_ = rhs.Unwrap(); + Py_XINCREF(rhs.Unwrap()); + } + + ~Object() { Py_XDECREF(m_py_object_); } + PyObject* Unwrap() const { return m_py_object_; } + bool IsNull() const { return m_py_object_ == NULL; } + static Object WrapExisting(PyObject* py_object) { return Object(py_object); } +}; + +inline Object Wrap(PyObject* py_object) +{ + return Object::WrapExisting(py_object); +} +inline Object Take(PyObject* py_object) +{ + Py_XINCREF(py_object); + return Object::WrapExisting(py_object); +} + +inline Object& Null() +{ + static Object null_obj = Wrap(nullptr); + return null_obj; +} + + +} + +namespace PyrArg +{ +template +bool ParseTupleSimple(PyObject* args, T1* arg1) +{ + return PyArg_ParseTuple(args, Py::fmt, arg1); +} + +template +bool ParseTupleSimple(PyObject* args, T1* arg1, T2* arg2) +{ + return PyArg_ParseTuple(args, Py::fmts.c_str(), arg1, arg2); +} +} + +namespace PyrImport +{ +inline Pyr::Object ImportModule(const char* module) +{ + return Pyr::Wrap(PyImport_ImportModule(module)); +} +} + +namespace PyrObject +{ +inline Pyr::Object GetAttrString(const Pyr::Object& py_object, const char* attr) +{ + return Pyr::Wrap(PyObject_GetAttrString(py_object.Unwrap(), attr)); +} +inline int SetAttrString(const Pyr::Object& py_object, const char* attr, const Pyr::Object& value) +{ + return PyObject_SetAttrString(py_object.Unwrap(), attr, value.Unwrap()); +} +inline Pyr::Object CallFunction(const Pyr::Object& callable_object, const char* format) +{ + return Pyr::Wrap(PyObject_CallFunction(callable_object.Unwrap(), format)); +} +inline Pyr::Object GetIter(const Pyr::Object& iterable_object) +{ + return Pyr::Wrap(PyObject_GetIter(iterable_object.Unwrap())); +} +} + +namespace PyrIter +{ +inline Pyr::Object Next(const Pyr::Object& iterable_object) +{ + return Pyr::Wrap(PyIter_Next(iterable_object.Unwrap())); +} +} + +namespace PyrErr +{ +inline const char* GetException() +{ + PyObject *ptype, *pvalue, *ptraceback; + PyErr_Fetch(&ptype, &pvalue, &ptraceback); + PyObject* repr = PyObject_Repr(pvalue); + PyObject* py_str = PyUnicode_AsEncodedString(repr, "utf-8", "Error ~"); + return PyBytes_AS_STRING(py_str); +} +} + +namespace PyrRun +{ +inline Pyr::Object File(std::string file, PyObject* globals, PyObject* locals) +{ + return Pyr::Wrap(PyRun_FileExFlags(fopen(file.c_str(), "r"), file.c_str(), Py_file_input, globals, + locals, true, nullptr)); +} +} + +namespace PyrGILState +{ +class GIL +{ + PyGILState_STATE gstate; +public: + GIL() + { + gstate = PyGILState_Ensure(); + } + ~GIL() + { + PyGILState_Release(gstate); + } +}; +} + diff --git a/Source/Core/Scripting/Python/aeventmodule.h b/Source/Core/Scripting/Python/aeventmodule.h new file mode 100644 index 000000000000..416010f29926 --- /dev/null +++ b/Source/Core/Scripting/Python/aeventmodule.h @@ -0,0 +1,57 @@ +#pragma once + +#include "Pyr.h" +#include "Python.h" + +namespace PyScripting +{ +inline const char* pycode = R"( + +@asyncio.coroutine +def aevent(event_name, *args): + return (yield (get_event_id(event_name), args)) + +@asyncio.coroutine +def frameadvance(): + return aevent("frameadvance") + +)"; + +inline PyObject* get_event_id(PyObject* self, PyObject* args) +{ + char* x; + if (!PyrArg::ParseTupleSimple(args, &x)) + return nullptr; + if (std::string(x) == "frameadvance") + return Py_BuildValue("l", 1); + return Py_BuildValue("l", 0); +} + +static PyMethodDef AEventMethods[] = { + {"get_event_id", get_event_id, METH_VARARGS, "gets the event id by event name"}, + {nullptr, nullptr, 0, nullptr} // Sentinel +}; + +static struct PyModuleDef aeventmodule = {PyModuleDef_HEAD_INIT, "aevent", nullptr, -1, + AEventMethods}; + +PyMODINIT_FUNC PyInit_aevent() +{ + PyObject* m = PyModule_Create(&aeventmodule); + if (m == nullptr) + return nullptr; + + PyObject* globals = PyModule_GetDict(m); + if (globals == nullptr) + return nullptr; + PyObject* asyncio_module = PyImport_ImportModuleEx("asyncio", globals, globals, nullptr); + if (asyncio_module == nullptr) + return nullptr; + if (PyDict_SetItemString(globals, "asyncio", asyncio_module) != 0) + return nullptr; + if (PyRun_String(pycode, Py_file_input, globals, globals) == nullptr) + return nullptr; + return m; +} + +} // namespace PyScripting diff --git a/Source/Core/Scripting/Python/doliomodule.h b/Source/Core/Scripting/Python/doliomodule.h new file mode 100644 index 000000000000..274002ebb09f --- /dev/null +++ b/Source/Core/Scripting/Python/doliomodule.h @@ -0,0 +1,107 @@ +#pragma once + +#include + +#include "Common/Logging/Log.h" +#include "Python.h" + +namespace PyScripting +{ + +static std::stringstream buffer_stdout; +static std::stringstream buffer_stderr; + +inline void flush_stdout() +{ + auto content = buffer_stdout.str(); + if (content.empty()) + return; + NOTICE_LOG(SCRIPTING, "Script stdout: %s", content.c_str()); + buffer_stdout.str(""); +} + +inline void flush_stderr() +{ + auto content = buffer_stderr.str(); + if (content.empty()) + return; + ERROR_LOG(SCRIPTING, "Script stderr: %s", content.c_str()); + buffer_stderr.str(""); +} + +inline PyObject* dol_write_stdout(PyObject* self, PyObject* args) +{ + const char* what; + if (!PyArg_ParseTuple(args, "s", &what)) + return nullptr; + for (auto i = 0; what[i] != '\0'; ++i) + { + if (what[i] == '\n') + flush_stdout(); + else + buffer_stdout << what[i]; + } + Py_RETURN_NONE; +} + +inline PyObject* dol_write_stderr(PyObject* self, PyObject* args) +{ + const char* what; + if (!PyArg_ParseTuple(args, "s", &what)) + return nullptr; + for (auto i = 0; what[i] != '\0'; ++i) + { + if (what[i] == '\n') + flush_stderr(); + else + buffer_stderr << what[i]; + } + Py_RETURN_NONE; +} + +inline PyObject* dol_flush_stdout(PyObject* self, PyObject* args) +{ + flush_stdout(); + Py_RETURN_NONE; +} + +inline PyObject* dol_flush_stderr(PyObject* self, PyObject* args) +{ + flush_stderr(); + Py_RETURN_NONE; +} + +static PyMethodDef IOMethodsStdout[] = { + {"write", dol_write_stdout, METH_VARARGS, "Writes to stdout"}, + {"flush", dol_flush_stdout, METH_VARARGS, "Flushes stdout"}, + {nullptr, nullptr, 0, nullptr} // Sentinel +}; + +static PyMethodDef IOMethodsStderr[] = { + {"write", dol_write_stderr, METH_VARARGS, "Writes to stderr"}, + {"flush", dol_flush_stderr, METH_VARARGS, "Flushes stderr"}, + {nullptr, nullptr, 0, nullptr} // Sentinel +}; + +static struct PyModuleDef iomodule_stdout = {PyModuleDef_HEAD_INIT, "dolio_stdout", nullptr, -1, IOMethodsStdout}; +static struct PyModuleDef iomodule_stderr = {PyModuleDef_HEAD_INIT, "dolio_stderr", nullptr, -1, IOMethodsStderr}; + +PyMODINIT_FUNC PyInit_dolio_stdout() +{ + PyObject* m = PyModule_Create(&iomodule_stdout); + if (m == nullptr) + return nullptr; + PySys_SetObject("stdout", m); + return m; +} + +PyMODINIT_FUNC PyInit_dolio_stderr() +{ + PyObject* m = PyModule_Create(&iomodule_stderr); + if (m == nullptr) + return nullptr; + PySys_SetObject("stderr", m); + return m; +} + +} // namespace PyScripting diff --git a/Source/Core/Scripting/Python/eventmodule.h b/Source/Core/Scripting/Python/eventmodule.h new file mode 100644 index 000000000000..a56531d82370 --- /dev/null +++ b/Source/Core/Scripting/Python/eventmodule.h @@ -0,0 +1,102 @@ +#pragma once + +#include "Core/API/Events.h" +#include "Pyr.h" + +namespace PyScripting +{ + +static Pyr::Object callback_onframeadvance = Pyr::Null(); + +static PyObject* event_onframeadvance(PyObject* self, PyObject* args) +{ + PyObject* temp; + if (!PyArg_ParseTuple(args, "O", &temp)) + return nullptr; + if (temp == Py_None) + { + callback_onframeadvance = Pyr::Null(); + Py_RETURN_NONE; + } + if (!PyCallable_Check(temp)) + return nullptr; + callback_onframeadvance = Pyr::Take(temp); + Py_RETURN_NONE; +} + +static PyMethodDef EventMethods[] = { + {"onframeadvance", event_onframeadvance, METH_VARARGS, "TODO"}, + {nullptr, nullptr, 0, nullptr} /* Sentinel */ +}; + +static struct PyModuleDef eventmodule = {PyModuleDef_HEAD_INIT, "event", nullptr, -1, EventMethods}; + +PyMODINIT_FUNC PyInit_event() +{ + return PyModule_Create(&eventmodule); +} + +static void OnFrameAdvance(const API::Events::FrameAdvance&) +{ + if (callback_onframeadvance.IsNull()) + return; + RunFromCPUThreadAsync([] { + PyrGILState::GIL lock; + const auto result = PyrObject::CallFunction(callback_onframeadvance, nullptr); + if (result.IsNull()) + PyErr_Print(); + }); +} + +static void OnMemory(const API::Events::MemoryBreakpoint& evt) +{ + //INFO_LOG(SCRIPTING, "on memory; write %d, address %u, value %u", evt.write, evt.addr, evt.value); +} + +/*static const char* GetInterruptName(u32 cause_mask) +{ + switch (cause_mask) + { + case ProcessorInterface::INT_CAUSE_PI: + return "INT_CAUSE_PI"; + case ProcessorInterface::INT_CAUSE_DI: + return "INT_CAUSE_DI"; + case ProcessorInterface::INT_CAUSE_RSW: + return "INT_CAUSE_RSW"; + case ProcessorInterface::INT_CAUSE_SI: + return "INT_CAUSE_SI"; + case ProcessorInterface::INT_CAUSE_EXI: + return "INT_CAUSE_EXI"; + case ProcessorInterface::INT_CAUSE_AI: + return "INT_CAUSE_AI"; + case ProcessorInterface::INT_CAUSE_DSP: + return "INT_CAUSE_DSP"; + case ProcessorInterface::INT_CAUSE_MEMORY: + return "INT_CAUSE_MEMORY"; + case ProcessorInterface::INT_CAUSE_VI: + return "INT_CAUSE_VI"; + case ProcessorInterface::INT_CAUSE_PE_TOKEN: + return "INT_CAUSE_PE_TOKEN"; + case ProcessorInterface::INT_CAUSE_PE_FINISH: + return "INT_CAUSE_PE_FINISH"; + case ProcessorInterface::INT_CAUSE_CP: + return "INT_CAUSE_CP"; + case ProcessorInterface::INT_CAUSE_DEBUG: + return "INT_CAUSE_DEBUG"; + case ProcessorInterface::INT_CAUSE_WII_IPC: + return "INT_CAUSE_WII_IPC"; + case ProcessorInterface::INT_CAUSE_HSP: + return "INT_CAUSE_HSP"; + case ProcessorInterface::INT_CAUSE_RST_BUTTON: + return "INT_CAUSE_RST_BUTTON"; + default: + return "!!! ERROR-unknown Interrupt !!!"; + } +}*/ + +static void OnInterrupt(const API::Events::SetInterrupt& evt) +{ + //INFO_LOG(SCRIPTING, "interrupt %s at frame %d", GetInterruptName(evt.cause_mask), Movie::GetCurrentFrame()); +} + +} // namespace PyScripting diff --git a/Source/Core/Scripting/Python/memorymodule.h b/Source/Core/Scripting/Python/memorymodule.h new file mode 100644 index 000000000000..a256ec9afe3f --- /dev/null +++ b/Source/Core/Scripting/Python/memorymodule.h @@ -0,0 +1,63 @@ +#pragma once + +#include "Core/HLE/HLE_VarArgs.h" +#include "Core/HW/Memmap.h" +#include "Core/PowerPC/BreakPoints.h" +#include "PyFramework/as_py_function.h" +#include "Python.h" + +namespace PyScripting +{ + +void add_memcheck(u32 addr, PyObject* callback) +{ + INFO_LOG(SCRIPTING, "adding memcheck at address %u", addr); + TMemCheck memcheck; + memcheck.start_address = addr; + memcheck.end_address = addr; + PowerPC::memchecks.Add(memcheck); +} + +static PyMethodDef MemoryMethods[] = { + {"read_u8", Py::as_py_function, METH_VARARGS, ""}, + {"read_u16", Py::as_py_function, METH_VARARGS, ""}, + {"read_u32", Py::as_py_function, METH_VARARGS, ""}, + {"read_u64", Py::as_py_function, METH_VARARGS, ""}, + + {"read_s8", Py::as_py_function, METH_VARARGS, ""}, + {"read_s16", Py::as_py_function, METH_VARARGS, ""}, + {"read_s32", Py::as_py_function, METH_VARARGS, ""}, + {"read_s64", Py::as_py_function, METH_VARARGS, ""}, + + {"read_f32", Py::as_py_function, METH_VARARGS, ""}, + {"read_f64", Py::as_py_function, METH_VARARGS, ""}, + + {"write_u8", Py::as_py_function, METH_VARARGS, ""}, + {"write_u16", Py::as_py_function, METH_VARARGS, ""}, + {"write_u32", Py::as_py_function, METH_VARARGS, ""}, + {"write_u64", Py::as_py_function, METH_VARARGS, ""}, + + {"write_s8", Py::as_py_function, METH_VARARGS, ""}, + {"write_s16", Py::as_py_function, METH_VARARGS, ""}, + {"write_s32", Py::as_py_function, METH_VARARGS, ""}, + {"write_s64", Py::as_py_function, METH_VARARGS, ""}, + + {"write_f32", Py::as_py_function, METH_VARARGS, ""}, + {"write_f64", Py::as_py_function, METH_VARARGS, ""}, + + {"add_memcheck", Py::as_py_function, METH_VARARGS, ""}, + + PyMethodDef{nullptr, nullptr, 0, nullptr} // Sentinel +}; + +static struct PyModuleDef memorymodule = {PyModuleDef_HEAD_INIT, "memory", nullptr, -1, + MemoryMethods}; + +PyMODINIT_FUNC PyInit_memory() +{ + // static PyModuleDef def = Py::MakeModule("memory", Py::ModuleFunc("foo", foo_test)); + + return PyModule_Create(&memorymodule); +} + +} // namespace PyScripting diff --git a/Source/Core/Scripting/Scripting.vcxproj b/Source/Core/Scripting/Scripting.vcxproj index 7b16b59b4cd6..5787c0cb4f0c 100644 --- a/Source/Core/Scripting/Scripting.vcxproj +++ b/Source/Core/Scripting/Scripting.vcxproj @@ -1,49 +1,65 @@ - - - - - Debug - x64 - - - Release - x64 - - - - {83794107-D372-4804-B463-E2719B50FB6B} - 10.0.15063.0 - - - - StaticLibrary - v141 - Unicode - - - true - - - false - - - - - - - - - - - - - $(ExternalsDir)zlib;$(ExternalsDir)python\include;%(AdditionalIncludeDirectories) - - - - - - - - + + + + + Debug + x64 + + + Release + x64 + + + + {83794107-D372-4804-B463-E2719B50FB6B} + 10.0.15063.0 + + + + StaticLibrary + v141 + Unicode + + + true + + + false + + + + + + + + + + + + + $(ExternalsDir)zlib;$(ExternalsDir)python\include;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Core/Scripting/Scripting.vcxproj.filters b/Source/Core/Scripting/Scripting.vcxproj.filters index e06016e9066f..c89df9f9e729 100644 --- a/Source/Core/Scripting/Scripting.vcxproj.filters +++ b/Source/Core/Scripting/Scripting.vcxproj.filters @@ -1,6 +1,50 @@ - - - - - + + + + + + Python + + + + + + Python + + + Python + + + Python + + + Python + + + Python + + + Python + + + Python\PyFramework + + + Python\PyFramework + + + Python\PyFramework + + + + + {4139d98d-00f6-4e16-a15b-68d5e3311cba} + + + {eeff862f-8e3a-4c8b-a5d3-35a5e8c992f2} + + + + + \ No newline at end of file diff --git a/Source/Core/Scripting/ScriptingEngine.cpp b/Source/Core/Scripting/ScriptingEngine.cpp new file mode 100644 index 000000000000..59d79c6e86b2 --- /dev/null +++ b/Source/Core/Scripting/ScriptingEngine.cpp @@ -0,0 +1,23 @@ + +#include "../Common/Logging/Log.h" + +#include "ScriptingEngine.h" +#include "Python/PyScriptingBackend.h" + + +namespace Scripting +{ + +void Init() +{ + INFO_LOG(SCRIPTING, "Initializing scripting engine..."); + PyScripting::Init(); +} + +void Shutdown() +{ + INFO_LOG(SCRIPTING, "Shutting down scripting engine..."); + PyScripting::Shutdown(); +} + +} // namespace Scripting diff --git a/Source/Core/Scripting/ScriptingEngine.h b/Source/Core/Scripting/ScriptingEngine.h new file mode 100644 index 000000000000..59d60b426f6d --- /dev/null +++ b/Source/Core/Scripting/ScriptingEngine.h @@ -0,0 +1,9 @@ +#pragma once + +namespace Scripting +{ + +void Init(); +void Shutdown(); + +} // namespace Scripting diff --git a/Source/UnitTests/Common/CMakeLists.txt b/Source/UnitTests/Common/CMakeLists.txt index 120fee6c3308..a2e96a820f98 100644 --- a/Source/UnitTests/Common/CMakeLists.txt +++ b/Source/UnitTests/Common/CMakeLists.txt @@ -14,6 +14,7 @@ add_dolphin_test(NandPathsTest NandPathsTest.cpp) add_dolphin_test(SPSCQueueTest SPSCQueueTest.cpp) add_dolphin_test(StringUtilTest StringUtilTest.cpp) add_dolphin_test(SwapTest SwapTest.cpp) +add_dolphin_test(TupleUtilTest TupleUtilTest.cpp) if (_M_X86) add_dolphin_test(x64EmitterTest x64EmitterTest.cpp) diff --git a/Source/UnitTests/Common/TupleUtilTest.cpp b/Source/UnitTests/Common/TupleUtilTest.cpp new file mode 100644 index 000000000000..f06a4a30e345 --- /dev/null +++ b/Source/UnitTests/Common/TupleUtilTest.cpp @@ -0,0 +1,46 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include + +#include "Common/TupleUtil.h" + +TEST(TupleUtil, MapToSize) +{ + struct Foo + { + }; + std::tuple tuple; + + std::tuple mapped = TupleMap(tuple, [](auto obj) { return sizeof(obj); }); + + EXPECT_EQ(std::get<0>(mapped), sizeof(double)); + EXPECT_EQ(std::get<1>(mapped), sizeof(std::string)); + EXPECT_EQ(std::get<2>(mapped), sizeof(Foo)); +} + +TEST(TupleUtil, MapToPointer) +{ + std::tuple tuple; + + std::tuple mapped = TupleMap(tuple, [](auto& obj) { return &obj; }); + + EXPECT_EQ(std::get<0>(tuple), 0); + *std::get<0>(mapped) = 42; + EXPECT_EQ(std::get<0>(tuple), 42); +} + +TEST(TupleUtil, TupleToArray) +{ + std::tuple tuple; + std::get<0>(tuple) = 14; + std::get<1>(tuple) = 42; + + std::array mapped = TupleToArray(tuple); + + EXPECT_EQ(mapped[0], 14); + EXPECT_EQ(mapped[1], 42); +}