Merge branch 'master' (early part) into medusa
9
CHANGES
|
@ -42,6 +42,7 @@ Misc:
|
|||
0.11.0: (Future)
|
||||
Features:
|
||||
- Scripting: New `input` API for getting raw keyboard/mouse/controller state
|
||||
- Scripting: New `storage` API for saving data for a script, e.g. settings
|
||||
- New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81
|
||||
- Debugger: Add range watchpoints
|
||||
Emulation fixes:
|
||||
|
@ -57,11 +58,19 @@ Other fixes:
|
|||
- Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes mgba.io/i/2794)
|
||||
- Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738)
|
||||
- Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560)
|
||||
- Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807)
|
||||
- Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702)
|
||||
- Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817)
|
||||
- Qt: Fix a handful of edge cases with graphics viewers (fixes mgba.io/i/2827)
|
||||
- Scripting: Fix receiving packets for client sockets
|
||||
- Scripting: Fix empty receive calls returning unknown error on Windows
|
||||
Misc:
|
||||
- GB Serialize: Add missing savestate support for MBC6 and NT (newer)
|
||||
- GBA: Improve detection of valid ELF ROMs
|
||||
- Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796)
|
||||
- Qt: Stop eating boolean action key events (fixes mgba.io/i/2636)
|
||||
- Qt: Automatically change video file extension as appropriate
|
||||
- Scripting: Add `callbacks:oneshot` for single-call callbacks
|
||||
|
||||
0.10.1: (2023-01-10)
|
||||
Emulation fixes:
|
||||
|
|
|
@ -54,6 +54,7 @@ if(NOT LIBMGBA_ONLY)
|
|||
set(USE_SQLITE3 ON CACHE BOOL "Whether or not to enable SQLite3 support")
|
||||
set(USE_ELF ON CACHE BOOL "Whether or not to enable ELF support")
|
||||
set(USE_LUA ON CACHE BOOL "Whether or not to enable Lua scripting support")
|
||||
set(USE_JSON_C ON CACHE BOOL "Whether or not to enable JSON-C support")
|
||||
set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core")
|
||||
set(M_CORE_GB ON CACHE BOOL "Build Game Boy core")
|
||||
set(M_CORE_DS ON CACHE BOOL "Build DS core")
|
||||
|
@ -481,6 +482,7 @@ endif()
|
|||
if(DISABLE_DEPS)
|
||||
set(USE_GDB_STUB OFF)
|
||||
set(USE_DISCORD_RPC OFF)
|
||||
set(USE_JSON_C OFF)
|
||||
set(USE_SQLITE3 OFF)
|
||||
set(USE_PNG OFF)
|
||||
set(USE_ZLIB OFF)
|
||||
|
@ -782,11 +784,36 @@ endif()
|
|||
|
||||
if(ENABLE_SCRIPTING)
|
||||
list(APPEND ENABLES SCRIPTING)
|
||||
find_feature(USE_JSON_C "json-c")
|
||||
if(NOT USE_LUA VERSION_LESS 5.1)
|
||||
find_feature(USE_LUA "Lua" ${USE_LUA})
|
||||
else()
|
||||
find_feature(USE_LUA "Lua")
|
||||
endif()
|
||||
if(USE_JSON_C)
|
||||
list(APPEND FEATURES JSON_C)
|
||||
if(TARGET json-c::json-c)
|
||||
list(APPEND DEPENDENCY_LIB json-c::json-c)
|
||||
get_target_property(JSON_C_SONAME json-c::json-c IMPORTED_SONAME_NONE)
|
||||
string(SUBSTRING "${JSON_C_SONAME}" 13 -1 JSON_C_SOVER)
|
||||
|
||||
# This is only needed on 0.15, but the target unhelpfully does not contain version info
|
||||
get_target_property(JSON_C_INCLUDE_DIR json-c::json-c INTERFACE_INCLUDE_DIRECTORIES)
|
||||
if(NOT JSON_C_INCLUDE_DIR MATCHES json-c$)
|
||||
include_directories(AFTER "${JSON_C_INCLUDE_DIR}/json-c")
|
||||
endif()
|
||||
else()
|
||||
if(${json-c_VERSION} VERSION_LESS 0.13.0)
|
||||
set(JSON_C_SOVER 3)
|
||||
elseif(${json-c_VERSION} VERSION_LESS 0.15.0)
|
||||
set(JSON_C_SOVER 4)
|
||||
endif()
|
||||
list(APPEND DEPENDENCY_LIB ${json-c_LIBRARIES})
|
||||
include_directories(AFTER ${json-c_INCLUDE_DIRS})
|
||||
link_directories(${json-c_LIBDIRS})
|
||||
endif()
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libjson-c${JSON_C_SOVER}")
|
||||
endif()
|
||||
if(USE_LUA)
|
||||
list(APPEND FEATURE_DEFINES USE_LUA)
|
||||
include_directories(AFTER ${LUA_INCLUDE_DIR})
|
||||
|
@ -1317,6 +1344,7 @@ if(NOT QUIET AND NOT LIBMGBA_ONLY)
|
|||
else()
|
||||
message(STATUS " Lua: ${USE_LUA}")
|
||||
endif()
|
||||
message(STATUS " storage API: ${USE_JSON_C}")
|
||||
endif()
|
||||
message(STATUS "Frontends:")
|
||||
message(STATUS " Qt: ${BUILD_QT}")
|
||||
|
|
|
@ -143,9 +143,9 @@ Compiling requires using CMake 3.1 or newer. GCC, Clang, and Visual Studio 2019
|
|||
|
||||
#### Docker building
|
||||
|
||||
The recommended way to build for most platforms is to use Docker. Several Docker images are provided that contain the requisite toolchain and dependencies for building mGBA across several platforms.
|
||||
The recommended way to build for most platforms is to use Docker. Several Docker images are provided that contain the requisite toolchain and dependencies for building mGBA across several platforms.
|
||||
|
||||
Note: If you are on an older Windows system before Windows 10, you may need to configure your Docker to use VirtualBox shared folders to correctly map your current `mgba` checkout directory to the Docker image's working directory. (See issue [#1985](https://mgba.io/i/1985) for details.)
|
||||
Note: If you are on an older Windows system before Windows 10, you may need to configure your Docker to use VirtualBox shared folders to correctly map your current `mgba` checkout directory to the Docker image's working directory. (See issue [#1985](https://mgba.io/i/1985) for details.)
|
||||
|
||||
To use a Docker image to build mGBA, simply run the following command while in the root of an mGBA checkout:
|
||||
|
||||
|
@ -252,6 +252,7 @@ medusa has no hard dependencies, however, the following optional dependencies ar
|
|||
- SQLite3: for game databases.
|
||||
- libelf: for ELF loading.
|
||||
- Lua: for scripting.
|
||||
- json-c: for the scripting `storage` API.
|
||||
|
||||
SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first.
|
||||
|
||||
|
@ -311,7 +312,7 @@ Missing features on DS are
|
|||
Copyright
|
||||
---------
|
||||
|
||||
medusa is Copyright © 2013 – 2022 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file.
|
||||
medusa is Copyright © 2013 – 2023 Jeffrey Pfau. It is distributed under the [Mozilla Public License version 2.0](https://www.mozilla.org/MPL/2.0/). A copy of the license is available in the distributed LICENSE file.
|
||||
|
||||
medusa contains the following third-party libraries:
|
||||
|
||||
|
|
|
@ -98,6 +98,7 @@ void mScriptContextExportNamespace(struct mScriptContext* context, const char* n
|
|||
|
||||
void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback, struct mScriptList* args);
|
||||
uint32_t mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value);
|
||||
uint32_t mScriptContextAddOneshot(struct mScriptContext*, const char* callback, struct mScriptValue* value);
|
||||
void mScriptContextRemoveCallback(struct mScriptContext*, uint32_t cbid);
|
||||
|
||||
void mScriptContextSetDocstring(struct mScriptContext*, const char* key, const char* docstring);
|
||||
|
|
|
@ -423,7 +423,7 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_DEFINE_STRUCT_DEINIT(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, _deinit)
|
||||
#define mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(TYPE, NAME) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, NAME)
|
||||
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(GET, TYPE, _get, _get)
|
||||
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, _set, _set)
|
||||
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE, SETTER) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, SETTER, SETTER)
|
||||
|
||||
#define mSCRIPT_DEFINE_DOC_STRUCT_METHOD(SCOPE, TYPE, NAME) mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(doc_ ## TYPE, NAME, NAME)
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef M_SCRIPT_STORAGE_H
|
||||
#define M_SCRIPT_STORAGE_H
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba/script/macros.h>
|
||||
|
||||
struct VFile;
|
||||
void mScriptContextAttachStorage(struct mScriptContext* context);
|
||||
void mScriptStorageFlushAll(struct mScriptContext* context);
|
||||
|
||||
bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucket);
|
||||
bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf);
|
||||
bool mScriptStorageLoadBucket(struct mScriptContext* context, const char* bucket);
|
||||
bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* bucket, struct VFile* vf);
|
||||
void mScriptStorageGetBucketPath(const char* bucket, char* out);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -35,16 +35,17 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_C_PTR void*
|
||||
#define mSCRIPT_TYPE_C_CPTR const void*
|
||||
#define mSCRIPT_TYPE_C_LIST struct mScriptList*
|
||||
#define mSCRIPT_TYPE_C_TABLE Table*
|
||||
#define mSCRIPT_TYPE_C_TABLE struct Table*
|
||||
#define mSCRIPT_TYPE_C_WRAPPER struct mScriptValue*
|
||||
#define mSCRIPT_TYPE_C_WEAKREF uint32_t
|
||||
#define mSCRIPT_TYPE_C_NUL void*
|
||||
#define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT*
|
||||
#define mSCRIPT_TYPE_C_CS(STRUCT) const struct STRUCT*
|
||||
#define mSCRIPT_TYPE_C_S_METHOD(STRUCT, NAME) _mSTStructFunctionType_ ## STRUCT ## _ ## NAME
|
||||
#define mSCRIPT_TYPE_C_PS(X) void
|
||||
#define mSCRIPT_TYPE_C_PCS(X) void
|
||||
#define mSCRIPT_TYPE_C_WSTR struct mScriptValue*
|
||||
#define mSCRIPT_TYPE_C_WLIST struct mScriptValue*
|
||||
#define mSCRIPT_TYPE_C_WTABLE struct mScriptValue*
|
||||
#define mSCRIPT_TYPE_C_W(X) struct mScriptValue*
|
||||
#define mSCRIPT_TYPE_C_CW(X) const struct mScriptValue*
|
||||
|
||||
|
@ -66,13 +67,14 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_FIELD_TABLE table
|
||||
#define mSCRIPT_TYPE_FIELD_WRAPPER opaque
|
||||
#define mSCRIPT_TYPE_FIELD_WEAKREF u32
|
||||
#define mSCRIPT_TYPE_FIELD_NUL opaque
|
||||
#define mSCRIPT_TYPE_FIELD_S(STRUCT) opaque
|
||||
#define mSCRIPT_TYPE_FIELD_CS(STRUCT) copaque
|
||||
#define mSCRIPT_TYPE_FIELD_S_METHOD(STRUCT, NAME) copaque
|
||||
#define mSCRIPT_TYPE_FIELD_PS(STRUCT) opaque
|
||||
#define mSCRIPT_TYPE_FIELD_PCS(STRUCT) copaque
|
||||
#define mSCRIPT_TYPE_FIELD_WSTR opaque
|
||||
#define mSCRIPT_TYPE_FIELD_WLIST opaque
|
||||
#define mSCRIPT_TYPE_FIELD_WTABLE opaque
|
||||
#define mSCRIPT_TYPE_FIELD_W(TYPE) opaque
|
||||
#define mSCRIPT_TYPE_FIELD_CW(TYPE) opaque
|
||||
|
||||
|
@ -94,13 +96,14 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_MS_TABLE (&mSTTable)
|
||||
#define mSCRIPT_TYPE_MS_WRAPPER (&mSTWrapper)
|
||||
#define mSCRIPT_TYPE_MS_WEAKREF (&mSTWeakref)
|
||||
#define mSCRIPT_TYPE_MS_NUL mSCRIPT_TYPE_MS_VOID
|
||||
#define mSCRIPT_TYPE_MS_S(STRUCT) (&mSTStruct_ ## STRUCT)
|
||||
#define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT)
|
||||
#define mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME) (&_mSTStructBindingType_ ## STRUCT ## _ ## NAME)
|
||||
#define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT)
|
||||
#define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructPtrConst_ ## STRUCT)
|
||||
#define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper)
|
||||
#define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper)
|
||||
#define mSCRIPT_TYPE_MS_WTABLE (&mSTTableWrapper)
|
||||
#define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE)
|
||||
#define mSCRIPT_TYPE_MS_CW(TYPE) (&mSTWrapperConst_ ## TYPE)
|
||||
#define mSCRIPT_TYPE_MS_DS(STRUCT) (&mSTStruct_doc_ ## STRUCT)
|
||||
|
@ -120,14 +123,16 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_CMP_STR(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_CHARP(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_LIST(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_TABLE(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_TABLE, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE)
|
||||
#define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true)
|
||||
#define mSCRIPT_TYPE_CMP_NUL(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_VOID, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME
|
||||
#define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME
|
||||
#define mSCRIPT_TYPE_CMP_S_METHOD(STRUCT, NAME) mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME)->name == _mSCRIPT_FIELD_NAME
|
||||
#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WSTR, TYPE))
|
||||
#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WLIST, TYPE))
|
||||
#define mSCRIPT_TYPE_CMP_WTABLE(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_WTABLE, TYPE))
|
||||
#define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1)
|
||||
#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE))
|
||||
#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE))
|
||||
|
||||
enum mScriptTypeBase {
|
||||
mSCRIPT_TYPE_VOID = 0,
|
||||
|
@ -183,6 +188,7 @@ extern const struct mScriptType mSTWrapper;
|
|||
extern const struct mScriptType mSTWeakref;
|
||||
extern const struct mScriptType mSTStringWrapper;
|
||||
extern const struct mScriptType mSTListWrapper;
|
||||
extern const struct mScriptType mSTTableWrapper;
|
||||
|
||||
extern struct mScriptValue mScriptValueNull;
|
||||
|
||||
|
@ -250,10 +256,10 @@ struct mScriptTypeClass {
|
|||
bool internal;
|
||||
struct Table instanceMembers;
|
||||
struct Table castToMembers;
|
||||
struct Table setters;
|
||||
struct mScriptClassMember* alloc; // TODO
|
||||
struct mScriptClassMember* free;
|
||||
struct mScriptClassMember* get;
|
||||
struct mScriptClassMember* set; // TODO
|
||||
};
|
||||
|
||||
struct mScriptType {
|
||||
|
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5"><path style="fill:#d8d6d1;stroke:#000;stroke-width:2.56px" d="M13.324 16.625h101.352v94.75H13.324z" transform="matrix(1.17413 0 0 1.1715 -11.144 -8.976)"/><path style="fill:#d8d6d1;stroke:#000;stroke-width:2.62px" d="M29.324 13.125h69.352v105.75H29.324z" transform="matrix(1.1968 0 0 1.08747 -12.595 -7.773)"/><path d="M93.426 105.2c0-.662-.596-1.2-1.33-1.2H35.904c-.734 0-1.33.538-1.33 1.2V128h58.852v-22.8z" style="fill:none;stroke:#000;stroke-width:.73px" transform="matrix(1.24041 0 0 1.375 -15.386 -53.5)"/><path d="M27 79.375h74" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.12162 0 0 1.11321 -7.784 -8.86)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 -7.375 -5.206)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 -7.375 6.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 -7.375 18.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 -7.375 30.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 -7.375 42.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 -7.375 54.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 -7.375 66.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 -7.375 78.78)"/><g><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 92.625 -5.206)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 92.625 6.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 92.625 18.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 92.625 30.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 92.625 42.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 92.625 54.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 92.625 66.78)"/><path d="m11 26.625 16-.025" style="fill:none;stroke:#000;stroke-width:.86px" transform="matrix(1.125 0 0 1.11652 92.625 78.78)"/></g><g><path d="M98.526 55.513c0-2.008-1.67-3.638-3.728-3.638H33.202c-2.058 0-3.728 1.63-3.728 3.638v16.974c0 2.008 1.67 3.638 3.728 3.638h61.596c2.058 0 3.728-1.63 3.728-3.638V55.513z" style="fill:#fff;stroke:#000;stroke-width:.87px" transform="matrix(1.08613 0 0 1.1134 -5.512 -9.258)"/></g><g><path d="M88.526 41.125c-16.351 6.018-32.701 6.1-49.052 0V16.992h49.052v24.133z" style="fill:none;stroke:#000;stroke-width:.89px" transform="matrix(1.08047 0 0 1.0637 -5.15 -11.307)"/></g><g><path d="M39.474 35.169c16.351 6.1 32.701 6.017 49.052 0v5.956c-16.351 6.018-32.701 6.1-49.052 0v-5.956z" style="fill:#4e5247;stroke:#000;stroke-width:.88px" transform="matrix(1.08047 0 0 1.10238 -5.15 -7.057)"/></g><g><path d="M53.875 102.875v-13.5" style="fill:none;stroke:#000;stroke-width:.98px" transform="matrix(1 0 0 .96296 .625 3.435)"/><path d="M53.875 102.875v-13.5" style="fill:none;stroke:#000;stroke-width:.98px" transform="matrix(1 0 0 .96296 19.625 3.435)"/></g></svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,143 @@
|
|||
/* MIT License
|
||||
*
|
||||
* Copyright (c) 2015-2023 Lior Halphon
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
/* Based on this (really good) article: http://blog.pkh.me/p/19-butchering-hqx-scaling-filters.html */
|
||||
|
||||
/* The colorspace used by the HQnx filters is not really YUV, despite the algorithm description claims it is. It is
|
||||
also not normalized. Therefore, we shall call the colorspace used by HQnx "HQ Colorspace" to avoid confusion. */
|
||||
varying vec2 texCoord;
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
|
||||
vec3 rgb_to_hq_colospace(vec4 rgb)
|
||||
{
|
||||
return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b,
|
||||
0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b,
|
||||
-0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b);
|
||||
}
|
||||
|
||||
bool is_different(vec4 a, vec4 b)
|
||||
{
|
||||
vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b));
|
||||
return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005;
|
||||
}
|
||||
|
||||
#define P(m, r) ((pattern & (m)) == (r))
|
||||
|
||||
vec4 interp_2px(vec4 c1, float w1, vec4 c2, float w2)
|
||||
{
|
||||
return (c1 * w1 + c2 * w2) / (w1 + w2);
|
||||
}
|
||||
|
||||
vec4 interp_3px(vec4 c1, float w1, vec4 c2, float w2, vec4 c3, float w3)
|
||||
{
|
||||
return (c1 * w1 + c2 * w2 + c3 * w3) / (w1 + w2 + w3);
|
||||
}
|
||||
|
||||
vec4 scale(sampler2D image, vec2 position, vec2 input_resolution)
|
||||
{
|
||||
// o = offset, the width of a pixel
|
||||
vec2 o = vec2(1, 1) / input_resolution;
|
||||
|
||||
/* We always calculate the top left pixel. If we need a different pixel, we flip the image */
|
||||
|
||||
// p = the position within a pixel [0...1]
|
||||
vec2 p = fract(position * input_resolution);
|
||||
|
||||
if (p.x > 0.5) o.x = -o.x;
|
||||
if (p.y > 0.5) o.y = -o.y;
|
||||
|
||||
vec4 w0 = texture2D(image, position + vec2( -o.x, -o.y));
|
||||
vec4 w1 = texture2D(image, position + vec2( 0, -o.y));
|
||||
vec4 w2 = texture2D(image, position + vec2( o.x, -o.y));
|
||||
vec4 w3 = texture2D(image, position + vec2( -o.x, 0));
|
||||
vec4 w4 = texture2D(image, position + vec2( 0, 0));
|
||||
vec4 w5 = texture2D(image, position + vec2( o.x, 0));
|
||||
vec4 w6 = texture2D(image, position + vec2( -o.x, o.y));
|
||||
vec4 w7 = texture2D(image, position + vec2( 0, o.y));
|
||||
vec4 w8 = texture2D(image, position + vec2( o.x, o.y));
|
||||
|
||||
int pattern = 0;
|
||||
if (is_different(w0, w4)) pattern |= 1;
|
||||
if (is_different(w1, w4)) pattern |= 2;
|
||||
if (is_different(w2, w4)) pattern |= 4;
|
||||
if (is_different(w3, w4)) pattern |= 8;
|
||||
if (is_different(w5, w4)) pattern |= 16;
|
||||
if (is_different(w6, w4)) pattern |= 32;
|
||||
if (is_different(w7, w4)) pattern |= 64;
|
||||
if (is_different(w8, w4)) pattern |= 128;
|
||||
|
||||
if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) {
|
||||
return interp_2px(w4, 3.0, w3, 1.0);
|
||||
}
|
||||
if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) {
|
||||
return interp_2px(w4, 3.0, w1, 1.0);
|
||||
}
|
||||
if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) {
|
||||
return w4;
|
||||
}
|
||||
if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) ||
|
||||
P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) ||
|
||||
P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) ||
|
||||
P(0xEB,0x8A)) && is_different(w3, w1)) {
|
||||
return interp_2px(w4, 3.0, w0, 1.0);
|
||||
}
|
||||
if (P(0x0B,0x08)) {
|
||||
return interp_3px(w4, 2.0, w0, 1.0, w1, 1.0);
|
||||
}
|
||||
if (P(0x0B,0x02)) {
|
||||
return interp_3px(w4, 2.0, w0, 1.0, w3, 1.0);
|
||||
}
|
||||
if (P(0x2F,0x2F)) {
|
||||
return interp_3px(w4, 4.0, w3, 1.0, w1, 1.0);
|
||||
}
|
||||
if (P(0xBF,0x37) || P(0xDB,0x13)) {
|
||||
return interp_3px(w4, 5.0, w1, 2.0, w3, 1.0);
|
||||
}
|
||||
if (P(0xDB,0x49) || P(0xEF,0x6D)) {
|
||||
return interp_3px(w4, 5.0, w3, 2.0, w1, 1.0);
|
||||
}
|
||||
if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) {
|
||||
return interp_2px(w4, 3.0, w3, 1.0);
|
||||
}
|
||||
if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) {
|
||||
return interp_2px(w4, 3.0, w1, 1.0);
|
||||
}
|
||||
if (P(0x7E,0x2A) || P(0xEF,0xAB) || P(0xBF,0x8F) || P(0x7E,0x0E)) {
|
||||
return interp_3px(w4, 2.0, w3, 3.0, w1, 3.0);
|
||||
}
|
||||
if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) ||
|
||||
P(0xDF,0xDE) || P(0xDF,0x1E)) {
|
||||
return interp_2px(w4, 3.0, w0, 1.0);
|
||||
}
|
||||
if (P(0x0A,0x00) || P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) ||
|
||||
P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) ||
|
||||
P(0x3B,0x1B)) {
|
||||
return interp_3px(w4, 2.0, w3, 1.0, w1, 1.0);
|
||||
}
|
||||
|
||||
return interp_3px(w4, 6.0, w3, 1.0, w1, 1.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
gl_FragColor = scale(tex, texCoord, texSize);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
[shader]
|
||||
name=hq2x
|
||||
author=Lior Halphon
|
||||
description="High Quality" 2x scaling
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
fragmentShader=hq2x.fs
|
||||
blend=0
|
||||
width=-2
|
||||
height=-2
|
|
@ -0,0 +1,9 @@
|
|||
[shader]
|
||||
name=OmniScale
|
||||
author=Lior Halphon
|
||||
description=Resolution-indepedent scaler inspired by the hqx family scalers
|
||||
passes=1
|
||||
|
||||
[pass.0]
|
||||
fragmentShader=omniscale.fs
|
||||
blend=0
|
|
@ -0,0 +1,292 @@
|
|||
/* MIT License
|
||||
*
|
||||
* Copyright (c) 2015-2023 Lior Halphon
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
/* OmniScale is derived from the pattern based design of HQnx, but with the following general differences:
|
||||
- The actual output calculating was completely redesigned as resolution independent graphic generator. This allows
|
||||
scaling to any factor.
|
||||
- HQnx approximations that were good enough for a 2x/3x/4x factor were refined, creating smoother gradients.
|
||||
- "Quarters" can be interpolated in more ways than in the HQnx filters
|
||||
- If a pattern does not provide enough information to determine the suitable scaling interpolation, up to 16 pixels
|
||||
per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation.
|
||||
*/
|
||||
varying vec2 texCoord;
|
||||
uniform sampler2D tex;
|
||||
uniform vec2 texSize;
|
||||
uniform vec2 outputSize;
|
||||
|
||||
/* We use the same colorspace as the HQ algorithms. */
|
||||
vec3 rgb_to_hq_colospace(vec4 rgb)
|
||||
{
|
||||
return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b,
|
||||
0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b,
|
||||
-0.125 * rgb.r + 0.250 * rgb.g - 0.125 * rgb.b);
|
||||
}
|
||||
|
||||
|
||||
bool is_different(vec4 a, vec4 b)
|
||||
{
|
||||
vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b));
|
||||
return diff.x > 0.018 || diff.y > 0.002 || diff.z > 0.005;
|
||||
}
|
||||
|
||||
#define P(m, r) ((pattern & (m)) == (r))
|
||||
|
||||
vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution)
|
||||
{
|
||||
// o = offset, the width of a pixel
|
||||
vec2 o = vec2(1, 1) / input_resolution;
|
||||
|
||||
/* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */
|
||||
|
||||
// p = the position within a pixel [0...1]
|
||||
vec2 p = fract(position * input_resolution);
|
||||
|
||||
if (p.x > 0.5) {
|
||||
o.x = -o.x;
|
||||
p.x = 1.0 - p.x;
|
||||
}
|
||||
if (p.y > 0.5) {
|
||||
o.y = -o.y;
|
||||
p.y = 1.0 - p.y;
|
||||
}
|
||||
|
||||
vec4 w0 = texture2D(image, position + vec2( -o.x, -o.y));
|
||||
vec4 w1 = texture2D(image, position + vec2( 0, -o.y));
|
||||
vec4 w2 = texture2D(image, position + vec2( o.x, -o.y));
|
||||
vec4 w3 = texture2D(image, position + vec2( -o.x, 0));
|
||||
vec4 w4 = texture2D(image, position + vec2( 0, 0));
|
||||
vec4 w5 = texture2D(image, position + vec2( o.x, 0));
|
||||
vec4 w6 = texture2D(image, position + vec2( -o.x, o.y));
|
||||
vec4 w7 = texture2D(image, position + vec2( 0, o.y));
|
||||
vec4 w8 = texture2D(image, position + vec2( o.x, o.y));
|
||||
|
||||
int pattern = 0;
|
||||
if (is_different(w0, w4)) pattern |= 1 << 0;
|
||||
if (is_different(w1, w4)) pattern |= 1 << 1;
|
||||
if (is_different(w2, w4)) pattern |= 1 << 2;
|
||||
if (is_different(w3, w4)) pattern |= 1 << 3;
|
||||
if (is_different(w5, w4)) pattern |= 1 << 4;
|
||||
if (is_different(w6, w4)) pattern |= 1 << 5;
|
||||
if (is_different(w7, w4)) pattern |= 1 << 6;
|
||||
if (is_different(w8, w4)) pattern |= 1 << 7;
|
||||
|
||||
if ((P(0xBF,0x37) || P(0xDB,0x13)) && is_different(w1, w5)) {
|
||||
return mix(w4, w3, 0.5 - p.x);
|
||||
}
|
||||
if ((P(0xDB,0x49) || P(0xEF,0x6D)) && is_different(w7, w3)) {
|
||||
return mix(w4, w1, 0.5 - p.y);
|
||||
}
|
||||
if ((P(0x0B,0x0B) || P(0xFE,0x4A) || P(0xFE,0x1A)) && is_different(w3, w1)) {
|
||||
return w4;
|
||||
}
|
||||
if ((P(0x6F,0x2A) || P(0x5B,0x0A) || P(0xBF,0x3A) || P(0xDF,0x5A) ||
|
||||
P(0x9F,0x8A) || P(0xCF,0x8A) || P(0xEF,0x4E) || P(0x3F,0x0E) ||
|
||||
P(0xFB,0x5A) || P(0xBB,0x8A) || P(0x7F,0x5A) || P(0xAF,0x8A) ||
|
||||
P(0xEB,0x8A)) && is_different(w3, w1)) {
|
||||
return mix(w4, mix(w4, w0, 0.5 - p.x), 0.5 - p.y);
|
||||
}
|
||||
if (P(0x0B,0x08)) {
|
||||
return mix(mix(w0 * 0.375 + w1 * 0.25 + w4 * 0.375, w4 * 0.5 + w1 * 0.5, p.x * 2.0), w4, p.y * 2.0);
|
||||
}
|
||||
if (P(0x0B,0x02)) {
|
||||
return mix(mix(w0 * 0.375 + w3 * 0.25 + w4 * 0.375, w4 * 0.5 + w3 * 0.5, p.y * 2.0), w4, p.x * 2.0);
|
||||
}
|
||||
if (P(0x2F,0x2F)) {
|
||||
float dist = length(p - vec2(0.5));
|
||||
float pixel_size = length(1.0 / (output_resolution / input_resolution));
|
||||
if (dist < 0.5 - pixel_size / 2) {
|
||||
return w4;
|
||||
}
|
||||
vec4 r;
|
||||
if (is_different(w0, w1) || is_different(w0, w3)) {
|
||||
r = mix(w1, w3, p.y - p.x + 0.5);
|
||||
}
|
||||
else {
|
||||
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
|
||||
}
|
||||
|
||||
if (dist > 0.5 + pixel_size / 2) {
|
||||
return r;
|
||||
}
|
||||
return mix(w4, r, (dist - 0.5 + pixel_size / 2) / pixel_size);
|
||||
}
|
||||
if (P(0xBF,0x37) || P(0xDB,0x13)) {
|
||||
float dist = p.x - 2.0 * p.y;
|
||||
float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0);
|
||||
if (dist > pixel_size / 2) {
|
||||
return w1;
|
||||
}
|
||||
vec4 r = mix(w3, w4, p.x + 0.5);
|
||||
if (dist < -pixel_size / 2) {
|
||||
return r;
|
||||
}
|
||||
return mix(r, w1, (dist + pixel_size / 2) / pixel_size);
|
||||
}
|
||||
if (P(0xDB,0x49) || P(0xEF,0x6D)) {
|
||||
float dist = p.y - 2.0 * p.x;
|
||||
float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0);
|
||||
if (p.y - 2.0 * p.x > pixel_size / 2) {
|
||||
return w3;
|
||||
}
|
||||
vec4 r = mix(w1, w4, p.x + 0.5);
|
||||
if (dist < -pixel_size / 2) {
|
||||
return r;
|
||||
}
|
||||
return mix(r, w3, (dist + pixel_size / 2) / pixel_size);
|
||||
}
|
||||
if (P(0xBF,0x8F) || P(0x7E,0x0E)) {
|
||||
float dist = p.x + 2.0 * p.y;
|
||||
float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0);
|
||||
|
||||
if (dist > 1.0 + pixel_size / 2) {
|
||||
return w4;
|
||||
}
|
||||
|
||||
vec4 r;
|
||||
if (is_different(w0, w1) || is_different(w0, w3)) {
|
||||
r = mix(w1, w3, p.y - p.x + 0.5);
|
||||
}
|
||||
else {
|
||||
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
|
||||
}
|
||||
|
||||
if (dist < 1.0 - pixel_size / 2) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size);
|
||||
}
|
||||
|
||||
if (P(0x7E,0x2A) || P(0xEF,0xAB)) {
|
||||
float dist = p.y + 2.0 * p.x;
|
||||
float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0);
|
||||
|
||||
if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2) {
|
||||
return w4;
|
||||
}
|
||||
|
||||
vec4 r;
|
||||
|
||||
if (is_different(w0, w1) || is_different(w0, w3)) {
|
||||
r = mix(w1, w3, p.y - p.x + 0.5);
|
||||
}
|
||||
else {
|
||||
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
|
||||
}
|
||||
|
||||
if (dist < 1.0 - pixel_size / 2) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size);
|
||||
}
|
||||
|
||||
if (P(0x1B,0x03) || P(0x4F,0x43) || P(0x8B,0x83) || P(0x6B,0x43)) {
|
||||
return mix(w4, w3, 0.5 - p.x);
|
||||
}
|
||||
|
||||
if (P(0x4B,0x09) || P(0x8B,0x89) || P(0x1F,0x19) || P(0x3B,0x19)) {
|
||||
return mix(w4, w1, 0.5 - p.y);
|
||||
}
|
||||
|
||||
if (P(0xFB,0x6A) || P(0x6F,0x6E) || P(0x3F,0x3E) || P(0xFB,0xFA) ||
|
||||
P(0xDF,0xDE) || P(0xDF,0x1E)) {
|
||||
return mix(w4, w0, (1.0 - p.x - p.y) / 2.0);
|
||||
}
|
||||
|
||||
if (P(0x4F,0x4B) || P(0x9F,0x1B) || P(0x2F,0x0B) ||
|
||||
P(0xBE,0x0A) || P(0xEE,0x0A) || P(0x7E,0x0A) || P(0xEB,0x4B) ||
|
||||
P(0x3B,0x1B)) {
|
||||
float dist = p.x + p.y;
|
||||
float pixel_size = length(1.0 / (output_resolution / input_resolution));
|
||||
|
||||
if (dist > 0.5 + pixel_size / 2) {
|
||||
return w4;
|
||||
}
|
||||
|
||||
vec4 r;
|
||||
if (is_different(w0, w1) || is_different(w0, w3)) {
|
||||
r = mix(w1, w3, p.y - p.x + 0.5);
|
||||
}
|
||||
else {
|
||||
r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0);
|
||||
}
|
||||
|
||||
if (dist < 0.5 - pixel_size / 2) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size);
|
||||
}
|
||||
|
||||
if (P(0x0B,0x01)) {
|
||||
return mix(mix(w4, w3, 0.5 - p.x), mix(w1, (w1 + w3) / 2.0, 0.5 - p.x), 0.5 - p.y);
|
||||
}
|
||||
|
||||
if (P(0x0B,0x00)) {
|
||||
return mix(mix(w4, w3, 0.5 - p.x), mix(w1, w0, 0.5 - p.x), 0.5 - p.y);
|
||||
}
|
||||
|
||||
float dist = p.x + p.y;
|
||||
float pixel_size = length(1.0 / (output_resolution / input_resolution));
|
||||
|
||||
if (dist > 0.5 + pixel_size / 2) {
|
||||
return w4;
|
||||
}
|
||||
|
||||
/* We need more samples to "solve" this diagonal */
|
||||
vec4 x0 = texture2D(image, position + vec2( -o.x * 2.0, -o.y * 2.0));
|
||||
vec4 x1 = texture2D(image, position + vec2( -o.x , -o.y * 2.0));
|
||||
vec4 x2 = texture2D(image, position + vec2( 0.0 , -o.y * 2.0));
|
||||
vec4 x3 = texture2D(image, position + vec2( o.x , -o.y * 2.0));
|
||||
vec4 x4 = texture2D(image, position + vec2( -o.x * 2.0, -o.y ));
|
||||
vec4 x5 = texture2D(image, position + vec2( -o.x * 2.0, 0.0 ));
|
||||
vec4 x6 = texture2D(image, position + vec2( -o.x * 2.0, o.y ));
|
||||
|
||||
if (is_different(x0, w4)) pattern |= 1 << 8;
|
||||
if (is_different(x1, w4)) pattern |= 1 << 9;
|
||||
if (is_different(x2, w4)) pattern |= 1 << 10;
|
||||
if (is_different(x3, w4)) pattern |= 1 << 11;
|
||||
if (is_different(x4, w4)) pattern |= 1 << 12;
|
||||
if (is_different(x5, w4)) pattern |= 1 << 13;
|
||||
if (is_different(x6, w4)) pattern |= 1 << 14;
|
||||
|
||||
int diagonal_bias = -7;
|
||||
while (pattern != 0) {
|
||||
diagonal_bias += pattern & 1;
|
||||
pattern >>= 1;
|
||||
}
|
||||
|
||||
if (diagonal_bias <= 0) {
|
||||
vec4 r = mix(w1, w3, p.y - p.x + 0.5);
|
||||
if (dist < 0.5 - pixel_size / 2) {
|
||||
return r;
|
||||
}
|
||||
return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size);
|
||||
}
|
||||
|
||||
return w4;
|
||||
}
|
||||
|
||||
void main() {
|
||||
gl_FragColor = scale(tex, texCoord, texSize, outputSize);
|
||||
}
|
|
@ -83,6 +83,10 @@
|
|||
#cmakedefine USE_GDB_STUB
|
||||
#endif
|
||||
|
||||
#ifndef USE_JSON_C
|
||||
#cmakedefine USE_JSON_C
|
||||
#endif
|
||||
|
||||
#ifndef USE_LIBAV
|
||||
#cmakedefine USE_LIBAV
|
||||
#endif
|
||||
|
|
|
@ -184,13 +184,13 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
|
|||
switch (sio->mode) {
|
||||
case SIO_NORMAL_8:
|
||||
case SIO_NORMAL_32:
|
||||
value |= 0x0004;
|
||||
value = GBASIONormalFillSi(value);
|
||||
if ((value & 0x0081) == 0x0081) {
|
||||
if (value & 0x4000) {
|
||||
if (GBASIONormalIsIrq(value)) {
|
||||
// TODO: Test this on hardware to see if this is correct
|
||||
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0);
|
||||
}
|
||||
value &= ~0x0080;
|
||||
value = GBASIONormalClearStart(value);
|
||||
}
|
||||
break;
|
||||
case SIO_MULTI:
|
||||
|
|
|
@ -346,7 +346,7 @@ static void _doDeferredSetup(void) {
|
|||
// On the off-hand chance that a core actually expects its buffers to be populated when
|
||||
// you actually first get them, you're out of luck without workarounds. Yup, seriously.
|
||||
// Here's that workaround, but really the API needs to be thrown out and rewritten.
|
||||
struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
|
||||
struct VFile* save = VFileFromMemory(savedata, GBA_SIZE_FLASH1M);
|
||||
if (!core->loadSave(core, save)) {
|
||||
save->close(save);
|
||||
}
|
||||
|
@ -931,8 +931,8 @@ bool retro_load_game(const struct retro_game_info* game) {
|
|||
core->setPeripheral(core, mPERIPH_RUMBLE, &rumble);
|
||||
core->setPeripheral(core, mPERIPH_ROTATION, &rotation);
|
||||
|
||||
savedata = anonymousMemoryMap(SIZE_CART_FLASH1M);
|
||||
memset(savedata, 0xFF, SIZE_CART_FLASH1M);
|
||||
savedata = anonymousMemoryMap(GBA_SIZE_FLASH1M);
|
||||
memset(savedata, 0xFF, GBA_SIZE_FLASH1M);
|
||||
|
||||
_reloadSettings();
|
||||
core->loadROM(core, rom);
|
||||
|
@ -1009,7 +1009,7 @@ void retro_unload_game(void) {
|
|||
core->deinit(core);
|
||||
mappedMemoryFree(data, dataSize);
|
||||
data = 0;
|
||||
mappedMemoryFree(savedata, SIZE_CART_FLASH1M);
|
||||
mappedMemoryFree(savedata, GBA_SIZE_FLASH1M);
|
||||
savedata = 0;
|
||||
}
|
||||
|
||||
|
@ -1164,7 +1164,7 @@ size_t retro_get_memory_size(unsigned id) {
|
|||
case mPLATFORM_GBA:
|
||||
switch (((struct GBA*) core->board)->memory.savedata.type) {
|
||||
case SAVEDATA_AUTODETECT:
|
||||
return SIZE_CART_FLASH1M;
|
||||
return GBA_SIZE_FLASH1M;
|
||||
default:
|
||||
return GBASavedataSize(&((struct GBA*) core->board)->memory.savedata);
|
||||
}
|
||||
|
@ -1363,7 +1363,7 @@ static void _startImage(struct mImageSource* image, unsigned w, unsigned h, int
|
|||
|
||||
static void _stopImage(struct mImageSource* image) {
|
||||
UNUSED(image);
|
||||
cam.stop();
|
||||
cam.stop();
|
||||
}
|
||||
|
||||
static void _requestImage(struct mImageSource* image, const void** buffer, size_t* stride, enum mColorFormat* colorFormat) {
|
||||
|
|
|
@ -301,7 +301,8 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
|
||||
glUseProgram(shader->program);
|
||||
glUniform1i(shader->texLocation, 0);
|
||||
glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH);
|
||||
glUniform2f(shader->texSizeLocation, context->d.width, context->d.height);
|
||||
glUniform2f(shader->outputSizeLocation, drawW, drawH);
|
||||
#ifdef BUILD_GLES3
|
||||
if (shader->vao != (GLuint) -1) {
|
||||
glBindVertexArray(shader->vao);
|
||||
|
@ -532,6 +533,7 @@ void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* f
|
|||
shader->texLocation = glGetUniformLocation(shader->program, "tex");
|
||||
shader->texSizeLocation = glGetUniformLocation(shader->program, "texSize");
|
||||
shader->positionLocation = glGetAttribLocation(shader->program, "position");
|
||||
shader->outputSizeLocation = glGetUniformLocation(shader->program, "outputSize");
|
||||
size_t i;
|
||||
for (i = 0; i < shader->nUniforms; ++i) {
|
||||
shader->uniforms[i].location = glGetUniformLocation(shader->program, shader->uniforms[i].name);
|
||||
|
@ -1010,8 +1012,11 @@ bool mGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) {
|
|||
}
|
||||
}
|
||||
u = mGLES2UniformListSize(&uniformVector);
|
||||
struct mGLES2Uniform* uniformBlock = calloc(u, sizeof(*uniformBlock));
|
||||
memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u);
|
||||
struct mGLES2Uniform* uniformBlock;
|
||||
if (u) {
|
||||
uniformBlock = calloc(u, sizeof(*uniformBlock));
|
||||
memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u);
|
||||
}
|
||||
mGLES2UniformListDeinit(&uniformVector);
|
||||
|
||||
mGLES2ShaderInit(&shaderBlock[n], vssrc, fssrc, width, height, scaling, uniformBlock, u);
|
||||
|
|
|
@ -70,6 +70,7 @@ struct mGLES2Shader {
|
|||
GLuint texLocation;
|
||||
GLuint texSizeLocation;
|
||||
GLuint positionLocation;
|
||||
GLuint outputSizeLocation;
|
||||
|
||||
struct mGLES2Uniform* uniforms;
|
||||
size_t nUniforms;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "input/GamepadButtonEvent.h"
|
||||
#include "InputIndex.h"
|
||||
#include "ShortcutController.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFontMetrics>
|
||||
|
@ -34,35 +35,7 @@ void KeyEditor::setValue(int key) {
|
|||
if (key < 0) {
|
||||
setText(tr("---"));
|
||||
} else {
|
||||
QKeySequence seq(key);
|
||||
switch (key) {
|
||||
#ifndef Q_OS_MAC
|
||||
case Qt::Key_Shift:
|
||||
setText(QCoreApplication::translate("QShortcut", "Shift"));
|
||||
break;
|
||||
case Qt::Key_Control:
|
||||
setText(QCoreApplication::translate("QShortcut", "Control"));
|
||||
break;
|
||||
case Qt::Key_Alt:
|
||||
setText(QCoreApplication::translate("QShortcut", "Alt"));
|
||||
break;
|
||||
case Qt::Key_Meta:
|
||||
setText(QCoreApplication::translate("QShortcut", "Meta"));
|
||||
break;
|
||||
#endif
|
||||
case Qt::Key_Super_L:
|
||||
setText(tr("Super (L)"));
|
||||
break;
|
||||
case Qt::Key_Super_R:
|
||||
setText(tr("Super (R)"));
|
||||
break;
|
||||
case Qt::Key_Menu:
|
||||
setText(tr("Menu"));
|
||||
break;
|
||||
default:
|
||||
setText(QKeySequence(key).toString(QKeySequence::NativeText));
|
||||
break;
|
||||
}
|
||||
setText(keyName(key));
|
||||
}
|
||||
}
|
||||
emit valueChanged(key);
|
||||
|
|
|
@ -42,7 +42,7 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
m_boundary = 2048;
|
||||
m_ui.tile->setMaxTile(3096);
|
||||
m_ui.tile->setMaxTile(3072);
|
||||
m_addressBase = GBA_BASE_VRAM;
|
||||
m_addressWidth = 8;
|
||||
m_ui.bgInfo->addCustomProperty("priority", tr("Priority"));
|
||||
|
@ -119,6 +119,9 @@ void MapView::selectMap(int map) {
|
|||
}
|
||||
m_map = map;
|
||||
m_mapStatus.fill({});
|
||||
// Different maps can have different max palette counts; set it to
|
||||
// 0 immediately to avoid tile lookups with state palette IDs break
|
||||
m_ui.tile->setPalette(0);
|
||||
updateTiles(true);
|
||||
}
|
||||
|
||||
|
@ -184,11 +187,18 @@ void MapView::updateTilesGBA(bool) {
|
|||
frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]);
|
||||
}
|
||||
}
|
||||
m_boundary = 1024;
|
||||
m_ui.tile->setMaxTile(1536);
|
||||
priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]);
|
||||
if (mode == 0 || (mode == 1 && m_map != 2)) {
|
||||
offset = QString("%1, %2")
|
||||
.arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)])
|
||||
.arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]);
|
||||
|
||||
if (!GBARegisterBGCNTIs256Color(io[(REG_BG0CNT >> 1) + m_map])) {
|
||||
m_boundary = 2048;
|
||||
m_ui.tile->setMaxTile(3072);
|
||||
}
|
||||
} else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) {
|
||||
int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)];
|
||||
refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16;
|
||||
|
|
|
@ -214,10 +214,6 @@ MultiplayerController::~MultiplayerController() {
|
|||
}
|
||||
|
||||
bool MultiplayerController::attachGame(CoreController* controller) {
|
||||
if (m_lockstep.attached == MAX_GBAS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_lockstep.attached == 0) {
|
||||
switch (controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
|
@ -233,6 +229,9 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
|||
default:
|
||||
return false;
|
||||
}
|
||||
m_platform = controller->platform();
|
||||
} else if (controller->platform() != m_platform) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mCoreThread* thread = controller->thread();
|
||||
|
@ -243,6 +242,10 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
|||
switch (controller->platform()) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA: {
|
||||
if (m_lockstep.attached >= MAX_GBAS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GBA* gba = static_cast<GBA*>(thread->core->board);
|
||||
|
||||
GBASIOLockstepNode* node = new GBASIOLockstepNode;
|
||||
|
@ -259,6 +262,10 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
|||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB: {
|
||||
if (m_lockstep.attached >= 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GB* gb = static_cast<GB*>(thread->core->board);
|
||||
|
||||
GBSIOLockstepNode* node = new GBSIOLockstepNode;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/lockstep.h>
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/internal/gba/sio/lockstep.h>
|
||||
|
@ -77,6 +78,8 @@ private:
|
|||
GBASIOLockstep m_gbaLockstep;
|
||||
#endif
|
||||
};
|
||||
|
||||
mPlatform m_platform = mPLATFORM_NONE;
|
||||
QList<Player> m_players;
|
||||
QMutex m_lock;
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "ShortcutModel.h"
|
||||
|
||||
#include "ShortcutController.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
|
@ -33,7 +34,7 @@ QVariant ShortcutModel::data(const QModelIndex& index, int role) const {
|
|||
case 0:
|
||||
return m_controller->visibleName(item->name);
|
||||
case 1:
|
||||
return shortcut ? QKeySequence(shortcut->shortcut()).toString(QKeySequence::NativeText) : QVariant();
|
||||
return shortcut ? keyName(shortcut->shortcut()) : QVariant();
|
||||
case 2:
|
||||
if (!shortcut) {
|
||||
return QVariant();
|
||||
|
@ -134,4 +135,4 @@ void ShortcutModel::clearMenu(const QString&) {
|
|||
// TODO
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ TileView::TileView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
m_ui.tile->setBoundary(2048, 0, 2);
|
||||
m_ui.tile->setMaxTile(3096);
|
||||
m_ui.tile->setMaxTile(3072);
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
|
@ -76,7 +76,7 @@ TileView::TileView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
m_ui.tile->setBoundary(2048 >> selected, selected, selected + 2);
|
||||
m_ui.tile->setMaxTile(3096 >> selected);
|
||||
m_ui.tile->setMaxTile(3072 >> selected);
|
||||
break;
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
|
|
|
@ -20,6 +20,7 @@ using namespace QGBA;
|
|||
QMap<QString, QString> VideoView::s_acodecMap;
|
||||
QMap<QString, QString> VideoView::s_vcodecMap;
|
||||
QMap<QString, QString> VideoView::s_containerMap;
|
||||
QMap<QString, QStringList> VideoView::s_extensionMap;
|
||||
|
||||
bool VideoView::Preset::compatible(const Preset& other) const {
|
||||
if (!other.container.isNull() && !container.isNull() && other.container != container) {
|
||||
|
@ -71,6 +72,23 @@ VideoView::VideoView(QWidget* parent)
|
|||
if (s_containerMap.empty()) {
|
||||
s_containerMap["mkv"] = "matroska";
|
||||
}
|
||||
if (s_extensionMap.empty()) {
|
||||
s_extensionMap["matroska"] += ".mkv";
|
||||
s_extensionMap["matroska"] += ".mka";
|
||||
s_extensionMap["webm"] += ".webm";
|
||||
s_extensionMap["avi"] += ".avi";
|
||||
s_extensionMap["mp4"] += ".mp4";
|
||||
s_extensionMap["mp4"] += ".m4v";
|
||||
s_extensionMap["mp4"] += ".m4a";
|
||||
|
||||
s_extensionMap["flac"] += ".flac";
|
||||
s_extensionMap["mpeg"] += ".mpg";
|
||||
s_extensionMap["mpeg"] += ".mpeg";
|
||||
s_extensionMap["mpegts"] += ".ts";
|
||||
s_extensionMap["mp3"] += ".mp3";
|
||||
s_extensionMap["ogg"] += ".ogg";
|
||||
s_extensionMap["ogv"] += ".ogv";
|
||||
}
|
||||
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &VideoView::close);
|
||||
connect(m_ui.start, &QAbstractButton::clicked, this, &VideoView::startRecording);
|
||||
|
@ -195,6 +213,9 @@ void VideoView::setController(std::shared_ptr<CoreController> controller) {
|
|||
}
|
||||
|
||||
void VideoView::startRecording() {
|
||||
if (QFileInfo(m_filename).suffix().isEmpty()) {
|
||||
changeExtension();
|
||||
}
|
||||
if (!validateSettings()) {
|
||||
return;
|
||||
}
|
||||
|
@ -242,6 +263,7 @@ void VideoView::selectFile() {
|
|||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select output file"));
|
||||
if (!filename.isEmpty()) {
|
||||
m_ui.filename->setText(filename);
|
||||
changeExtension();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,6 +315,7 @@ void VideoView::setContainer(const QString& container) {
|
|||
m_containerCstr = nullptr;
|
||||
m_container = QString();
|
||||
}
|
||||
changeExtension();
|
||||
validateSettings();
|
||||
uncheckIncompatible();
|
||||
}
|
||||
|
@ -462,6 +485,30 @@ void VideoView::uncheckIncompatible() {
|
|||
}
|
||||
}
|
||||
|
||||
void VideoView::changeExtension() {
|
||||
if (m_filename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s_extensionMap.contains(m_container)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList extensions = s_extensionMap.value(m_container);
|
||||
QString filename = m_filename;
|
||||
int index = m_filename.lastIndexOf(".");
|
||||
if (index >= 0) {
|
||||
if (extensions.contains(filename.mid(index))) {
|
||||
// This extension is already valid
|
||||
return;
|
||||
}
|
||||
filename.truncate(index);
|
||||
}
|
||||
filename += extensions.front();
|
||||
|
||||
m_ui.filename->setText(filename);
|
||||
}
|
||||
|
||||
QString VideoView::sanitizeCodec(const QString& codec, const QMap<QString, QString>& mapping) {
|
||||
QString sanitized = codec.toLower();
|
||||
sanitized = sanitized.remove(QChar('.'));
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#ifdef USE_FFMPEG
|
||||
|
||||
#include <QStringList>
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
@ -63,6 +64,8 @@ private slots:
|
|||
void uncheckIncompatible();
|
||||
void updatePresets();
|
||||
|
||||
void changeExtension();
|
||||
|
||||
private:
|
||||
struct Preset {
|
||||
QString container;
|
||||
|
@ -124,6 +127,7 @@ private:
|
|||
static QMap<QString, QString> s_acodecMap;
|
||||
static QMap<QString, QString> s_vcodecMap;
|
||||
static QMap<QString, QString> s_containerMap;
|
||||
static QMap<QString, QStringList> s_extensionMap;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "scripting/ScriptingTextBufferModel.h"
|
||||
|
||||
#include <mgba/script/input.h>
|
||||
#include <mgba/script/storage.h>
|
||||
#include <mgba-util/math.h>
|
||||
#include <mgba-util/string.h>
|
||||
|
||||
|
@ -51,6 +52,9 @@ ScriptingController::ScriptingController(QObject* parent)
|
|||
m_bufferModel = new ScriptingTextBufferModel(this);
|
||||
QObject::connect(m_bufferModel, &ScriptingTextBufferModel::textBufferCreated, this, &ScriptingController::textBufferCreated);
|
||||
|
||||
connect(&m_storageFlush, &QTimer::timeout, this, &ScriptingController::flushStorage);
|
||||
m_storageFlush.setInterval(5);
|
||||
|
||||
mScriptGamepadInit(&m_gamepad);
|
||||
|
||||
init();
|
||||
|
@ -107,9 +111,11 @@ bool ScriptingController::load(VFileDevice& vf, const QString& name) {
|
|||
emit error(QString::fromUtf8(m_activeEngine->getError(m_activeEngine)));
|
||||
ok = false;
|
||||
}
|
||||
if (m_controller && m_controller->isPaused()) {
|
||||
if (m_controller) {
|
||||
m_controller->setSync(true);
|
||||
m_controller->paused();
|
||||
if (m_controller->isPaused()) {
|
||||
m_controller->paused();
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
@ -144,6 +150,12 @@ void ScriptingController::runCode(const QString& code) {
|
|||
load(vf, "*prompt");
|
||||
}
|
||||
|
||||
void ScriptingController::flushStorage() {
|
||||
#ifdef USE_JSON_C
|
||||
mScriptStorageFlushAll(&m_scriptContext);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ScriptingController::eventFilter(QObject* obj, QEvent* ev) {
|
||||
event(obj, ev);
|
||||
return false;
|
||||
|
@ -293,6 +305,9 @@ void ScriptingController::detachGamepad() {
|
|||
void ScriptingController::init() {
|
||||
mScriptContextInit(&m_scriptContext);
|
||||
mScriptContextAttachStdlib(&m_scriptContext);
|
||||
#ifdef USE_JSON_C
|
||||
mScriptContextAttachStorage(&m_scriptContext);
|
||||
#endif
|
||||
mScriptContextAttachSocket(&m_scriptContext);
|
||||
mScriptContextAttachInput(&m_scriptContext);
|
||||
mScriptContextRegisterEngines(&m_scriptContext);
|
||||
|
@ -308,6 +323,10 @@ void ScriptingController::init() {
|
|||
if (m_engines.count() == 1) {
|
||||
m_activeEngine = *m_engines.begin();
|
||||
}
|
||||
|
||||
#ifdef USE_JSON_C
|
||||
m_storageFlush.start();
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t ScriptingController::qtToScriptingKey(const QKeyEvent* event) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba/script/input.h>
|
||||
|
@ -55,6 +56,8 @@ public slots:
|
|||
void reset();
|
||||
void runCode(const QString& code);
|
||||
|
||||
void flushStorage();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject*, QEvent*) override;
|
||||
|
||||
|
@ -84,6 +87,8 @@ private:
|
|||
|
||||
std::shared_ptr<CoreController> m_controller;
|
||||
InputController* m_inputController = nullptr;
|
||||
|
||||
QTimer m_storageFlush;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "utils.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QKeySequence>
|
||||
#include <QObject>
|
||||
|
||||
#include "VFileDevice.h"
|
||||
|
@ -145,4 +146,27 @@ bool extractMatchingFile(VDir* dir, std::function<QString (VDirEntry*)> filter)
|
|||
return false;
|
||||
}
|
||||
|
||||
QString keyName(int key) {
|
||||
switch (key) {
|
||||
#ifndef Q_OS_MAC
|
||||
case Qt::Key_Shift:
|
||||
return QCoreApplication::translate("QShortcut", "Shift");
|
||||
case Qt::Key_Control:
|
||||
return QCoreApplication::translate("QShortcut", "Control");
|
||||
case Qt::Key_Alt:
|
||||
return QCoreApplication::translate("QShortcut", "Alt");
|
||||
case Qt::Key_Meta:
|
||||
return QCoreApplication::translate("QShortcut", "Meta");
|
||||
#endif
|
||||
case Qt::Key_Super_L:
|
||||
return QObject::tr("Super (L)");
|
||||
case Qt::Key_Super_R:
|
||||
return QObject::tr("Super (R)");
|
||||
case Qt::Key_Menu:
|
||||
return QObject::tr("Menu");
|
||||
default:
|
||||
return QKeySequence(key).toString(QKeySequence::NativeText);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -85,4 +85,6 @@ constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
|
|||
QString romFilters(bool includeMvl = false);
|
||||
bool extractMatchingFile(VDir* dir, std::function<QString (VDirEntry*)> filter);
|
||||
|
||||
QString keyName(int key);
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ set(TEST_FILES
|
|||
test/classes.c
|
||||
test/types.c)
|
||||
|
||||
if(USE_JSON_C)
|
||||
list(APPEND SOURCE_FILES storage.c)
|
||||
endif()
|
||||
|
||||
if(USE_LUA)
|
||||
list(APPEND SOURCE_FILES engines/lua.c)
|
||||
list(APPEND TEST_FILES
|
||||
|
@ -17,6 +21,10 @@ if(USE_LUA)
|
|||
test/input.c
|
||||
test/lua.c
|
||||
test/stdlib.c)
|
||||
|
||||
if(USE_JSON_C)
|
||||
list(APPEND TEST_FILES test/storage.c)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
source_group("Scripting" FILES ${SOURCE_FILES})
|
||||
|
|
|
@ -17,8 +17,10 @@ struct mScriptFileInfo {
|
|||
};
|
||||
|
||||
struct mScriptCallbackInfo {
|
||||
struct mScriptValue* fn;
|
||||
const char* callback;
|
||||
size_t id;
|
||||
uint32_t id;
|
||||
bool oneshot;
|
||||
};
|
||||
|
||||
static void _engineContextDestroy(void* ctx) {
|
||||
|
@ -56,13 +58,28 @@ static void _contextFindForFile(const char* key, void* value, void* user) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _freeTable(void* data) {
|
||||
struct Table* table = data;
|
||||
|
||||
struct TableIterator iter;
|
||||
if (TableIteratorStart(table, &iter)) {
|
||||
do {
|
||||
struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter);
|
||||
mScriptValueDeref(info->fn);
|
||||
} while (TableIteratorNext(table, &iter));
|
||||
}
|
||||
|
||||
TableDeinit(table);
|
||||
free(table);
|
||||
}
|
||||
|
||||
void mScriptContextInit(struct mScriptContext* context) {
|
||||
HashTableInit(&context->rootScope, 0, (void (*)(void*)) mScriptValueDeref);
|
||||
HashTableInit(&context->engines, 0, _engineContextDestroy);
|
||||
mScriptListInit(&context->refPool, 0);
|
||||
TableInit(&context->weakrefs, 0, (void (*)(void*)) mScriptValueDeref);
|
||||
context->nextWeakref = 1;
|
||||
HashTableInit(&context->callbacks, 0, (void (*)(void*)) mScriptValueDeref);
|
||||
HashTableInit(&context->callbacks, 0, _freeTable);
|
||||
TableInit(&context->callbackId, 0, free);
|
||||
context->nextCallbackId = 1;
|
||||
context->constants = NULL;
|
||||
|
@ -84,14 +101,6 @@ void mScriptContextFillPool(struct mScriptContext* context, struct mScriptValue*
|
|||
if (value->refs == mSCRIPT_VALUE_UNREF) {
|
||||
return;
|
||||
}
|
||||
switch (value->type->base) {
|
||||
case mSCRIPT_TYPE_SINT:
|
||||
case mSCRIPT_TYPE_UINT:
|
||||
case mSCRIPT_TYPE_FLOAT:
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
struct mScriptValue* poolEntry = mScriptListAppend(&context->refPool);
|
||||
poolEntry->type = mSCRIPT_TYPE_MS_WRAPPER;
|
||||
|
@ -212,45 +221,60 @@ void mScriptContextDisownWeakref(struct mScriptContext* context, uint32_t weakre
|
|||
}
|
||||
|
||||
void mScriptContextTriggerCallback(struct mScriptContext* context, const char* callback, struct mScriptList* args) {
|
||||
struct mScriptValue* list = HashTableLookup(&context->callbacks, callback);
|
||||
if (!list) {
|
||||
struct Table* table = HashTableLookup(&context->callbacks, callback);
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
size_t i;
|
||||
for (i = 0; i < mScriptListSize(list->value.list); ++i) {
|
||||
struct TableIterator iter;
|
||||
if (!TableIteratorStart(table, &iter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct UInt32List oneshots;
|
||||
UInt32ListInit(&oneshots, 0);
|
||||
do {
|
||||
struct mScriptFrame frame;
|
||||
struct mScriptValue* fn = mScriptListGetPointer(list->value.list, i);
|
||||
if (!fn->type) {
|
||||
continue;
|
||||
}
|
||||
struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter);
|
||||
mScriptFrameInit(&frame);
|
||||
if (args) {
|
||||
mScriptListCopy(&frame.arguments, args);
|
||||
}
|
||||
if (fn->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
fn = mScriptValueUnwrap(fn);
|
||||
}
|
||||
mScriptInvoke(fn, &frame);
|
||||
mScriptInvoke(info->fn, &frame);
|
||||
mScriptFrameDeinit(&frame);
|
||||
|
||||
if (info->oneshot) {
|
||||
*UInt32ListAppend(&oneshots) = info->id;
|
||||
}
|
||||
} while (TableIteratorNext(table, &iter));
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < UInt32ListSize(&oneshots); ++i) {
|
||||
mScriptContextRemoveCallback(context, *UInt32ListGetPointer(&oneshots, i));
|
||||
}
|
||||
UInt32ListDeinit(&oneshots);
|
||||
}
|
||||
|
||||
uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) {
|
||||
static uint32_t mScriptContextAddCallbackInternal(struct mScriptContext* context, const char* callback, struct mScriptValue* fn, bool oneshot) {
|
||||
if (fn->type->base != mSCRIPT_TYPE_FUNCTION) {
|
||||
return 0;
|
||||
}
|
||||
struct mScriptValue* list = HashTableLookup(&context->callbacks, callback);
|
||||
if (!list) {
|
||||
list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST);
|
||||
HashTableInsert(&context->callbacks, callback, list);
|
||||
struct Table* table = HashTableLookup(&context->callbacks, callback);
|
||||
if (!table) {
|
||||
table = calloc(1, sizeof(*table));
|
||||
TableInit(table, 0, NULL);
|
||||
HashTableInsert(&context->callbacks, callback, table);
|
||||
}
|
||||
struct mScriptCallbackInfo* info = malloc(sizeof(*info));
|
||||
// Steal the string from the table key, since it's guaranteed to outlive this struct
|
||||
struct TableIterator iter;
|
||||
HashTableIteratorLookup(&context->callbacks, &iter, callback);
|
||||
info->callback = HashTableIteratorGetKey(&context->callbacks, &iter);
|
||||
info->id = mScriptListSize(list->value.list);
|
||||
mScriptValueWrap(fn, mScriptListAppend(list->value.list));
|
||||
info->oneshot = oneshot;
|
||||
if (fn->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
fn = mScriptValueUnwrap(fn);
|
||||
}
|
||||
info->fn = fn;
|
||||
mScriptValueRef(fn);
|
||||
while (true) {
|
||||
uint32_t id = context->nextCallbackId;
|
||||
++context->nextCallbackId;
|
||||
|
@ -258,8 +282,19 @@ uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* c
|
|||
continue;
|
||||
}
|
||||
TableInsert(&context->callbackId, id, info);
|
||||
return id;
|
||||
info->id = id;
|
||||
break;
|
||||
}
|
||||
TableInsert(table, info->id, info);
|
||||
return info->id;
|
||||
}
|
||||
|
||||
uint32_t mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) {
|
||||
return mScriptContextAddCallbackInternal(context, callback, fn, false);
|
||||
}
|
||||
|
||||
uint32_t mScriptContextAddOneshot(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) {
|
||||
return mScriptContextAddCallbackInternal(context, callback, fn, true);
|
||||
}
|
||||
|
||||
void mScriptContextRemoveCallback(struct mScriptContext* context, uint32_t cbid) {
|
||||
|
@ -267,16 +302,13 @@ void mScriptContextRemoveCallback(struct mScriptContext* context, uint32_t cbid)
|
|||
if (!info) {
|
||||
return;
|
||||
}
|
||||
struct mScriptValue* list = HashTableLookup(&context->callbacks, info->callback);
|
||||
if (!list) {
|
||||
struct Table* table = HashTableLookup(&context->callbacks, info->callback);
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
if (info->id >= mScriptListSize(list->value.list)) {
|
||||
return;
|
||||
}
|
||||
struct mScriptValue* fn = mScriptValueUnwrap(mScriptListGetPointer(list->value.list, info->id));
|
||||
mScriptValueDeref(fn);
|
||||
mScriptListGetPointer(list->value.list, info->id)->type = NULL;
|
||||
mScriptValueDeref(info->fn);
|
||||
TableRemove(table, cbid);
|
||||
TableRemove(&context->callbackId, cbid);
|
||||
}
|
||||
|
||||
void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants) {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <mgba/internal/script/types.h>
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba/script/input.h>
|
||||
#include <mgba/script/storage.h>
|
||||
#include <mgba-util/string.h>
|
||||
|
||||
struct mScriptContext context;
|
||||
|
@ -469,6 +470,7 @@ int main(int argc, char* argv[]) {
|
|||
mScriptContextInit(&context);
|
||||
mScriptContextAttachStdlib(&context);
|
||||
mScriptContextAttachSocket(&context);
|
||||
mScriptContextAttachStorage(&context);
|
||||
mScriptContextAttachInput(&context);
|
||||
mScriptContextSetTextBufferFactory(&context, NULL, NULL);
|
||||
|
||||
|
|
|
@ -36,8 +36,11 @@ static const char* _luaGetError(struct mScriptEngineContext*);
|
|||
|
||||
static bool _luaCall(struct mScriptFrame*, void* context);
|
||||
|
||||
static void _freeFrame(struct mScriptList* frame);
|
||||
static void _autofreeFrame(struct mScriptContext* context, struct mScriptList* frame);
|
||||
|
||||
struct mScriptEngineContextLua;
|
||||
static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*, bool internal);
|
||||
static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*);
|
||||
static bool _luaPopFrame(struct mScriptEngineContextLua*, struct mScriptList*);
|
||||
static bool _luaInvoke(struct mScriptEngineContextLua*, struct mScriptFrame*);
|
||||
|
||||
|
@ -131,7 +134,7 @@ static const char* _socketLuaSource =
|
|||
" end,\n"
|
||||
" connect = function(self, address, port)\n"
|
||||
" local status = self._s:connect(address, port)\n"
|
||||
" return socket._wrap(status)\n"
|
||||
" return self:_hook(status)\n"
|
||||
" end,\n"
|
||||
" listen = function(self, backlog)\n"
|
||||
" local status = self._s:listen(backlog or 1)\n"
|
||||
|
@ -520,6 +523,8 @@ struct mScriptValue* _luaRootScope(struct mScriptEngineContext* ctx) {
|
|||
lua_pop(luaContext->lua, 1);
|
||||
key = _luaCoerce(luaContext, false);
|
||||
mScriptValueWrap(key, mScriptListAppend(list->value.list));
|
||||
mScriptValueRef(key);
|
||||
mScriptContextFillPool(luaContext->d.context, key);
|
||||
}
|
||||
lua_pop(luaContext->lua, 1);
|
||||
|
||||
|
@ -538,11 +543,13 @@ struct mScriptValue* _luaCoerceFunction(struct mScriptEngineContextLua* luaConte
|
|||
return value;
|
||||
}
|
||||
|
||||
struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext) {
|
||||
struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext, struct Table* markedObjects) {
|
||||
struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
|
||||
bool isList = true;
|
||||
|
||||
lua_pushnil(luaContext->lua);
|
||||
|
||||
const void* tablePointer;
|
||||
while (lua_next(luaContext->lua, -2) != 0) {
|
||||
struct mScriptValue* value = NULL;
|
||||
int type = lua_type(luaContext->lua, -1);
|
||||
|
@ -553,14 +560,20 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext)
|
|||
case LUA_TFUNCTION:
|
||||
value = _luaCoerce(luaContext, true);
|
||||
break;
|
||||
case LUA_TTABLE:
|
||||
tablePointer = lua_topointer(luaContext->lua, -1);
|
||||
// Ensure this table doesn't contain any cycles
|
||||
if (!HashTableLookupBinary(markedObjects, &tablePointer, sizeof(tablePointer))) {
|
||||
HashTableInsertBinary(markedObjects, &tablePointer, sizeof(tablePointer), (void*) tablePointer);
|
||||
value = _luaCoerceTable(luaContext, markedObjects);
|
||||
}
|
||||
default:
|
||||
// Don't let values be something that could contain themselves
|
||||
break;
|
||||
}
|
||||
if (!value) {
|
||||
lua_pop(luaContext->lua, 3);
|
||||
lua_pop(luaContext->lua, type == LUA_TTABLE ? 2 : 3);
|
||||
mScriptValueDeref(table);
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mScriptValue* key = NULL;
|
||||
|
@ -584,18 +597,13 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext)
|
|||
return false;
|
||||
}
|
||||
mScriptTableInsert(table, key, value);
|
||||
if (key->type != mSCRIPT_TYPE_MS_STR) {
|
||||
// Strings are added to the ref pool, so we need to keep it
|
||||
// ref'd to prevent it from being collected prematurely
|
||||
mScriptValueDeref(key);
|
||||
}
|
||||
mScriptValueDeref(key);
|
||||
mScriptValueDeref(value);
|
||||
}
|
||||
lua_pop(luaContext->lua, 1);
|
||||
|
||||
size_t len = mScriptTableSize(table);
|
||||
if (!isList || !len) {
|
||||
mScriptContextFillPool(luaContext->d.context, table);
|
||||
return table;
|
||||
}
|
||||
|
||||
|
@ -605,18 +613,15 @@ struct mScriptValue* _luaCoerceTable(struct mScriptEngineContextLua* luaContext)
|
|||
struct mScriptValue* value = mScriptTableLookup(table, &mSCRIPT_MAKE_S64(i));
|
||||
if (!value) {
|
||||
mScriptValueDeref(list);
|
||||
mScriptContextFillPool(luaContext->d.context, table);
|
||||
return table;
|
||||
}
|
||||
mScriptValueWrap(value, mScriptListAppend(list->value.list));
|
||||
}
|
||||
if (i != len + 1) {
|
||||
mScriptValueDeref(list);
|
||||
mScriptContextFillPool(luaContext->d.context, table);
|
||||
return table;
|
||||
}
|
||||
mScriptValueDeref(table);
|
||||
mScriptContextFillPool(luaContext->d.context, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
|
@ -628,6 +633,7 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool
|
|||
|
||||
size_t size;
|
||||
const void* buffer;
|
||||
struct Table markedObjects;
|
||||
struct mScriptValue* value = NULL;
|
||||
switch (lua_type(luaContext->lua, -1)) {
|
||||
case LUA_TNIL:
|
||||
|
@ -651,7 +657,6 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool
|
|||
case LUA_TSTRING:
|
||||
buffer = lua_tolstring(luaContext->lua, -1, &size);
|
||||
value = mScriptStringCreateFromBytes(buffer, size);
|
||||
mScriptContextFillPool(luaContext->d.context, value);
|
||||
break;
|
||||
case LUA_TFUNCTION:
|
||||
// This function pops the value internally via luaL_ref
|
||||
|
@ -664,19 +669,36 @@ struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext, bool
|
|||
if (!pop) {
|
||||
break;
|
||||
}
|
||||
return _luaCoerceTable(luaContext);
|
||||
HashTableInit(&markedObjects, 0, NULL);
|
||||
value = _luaCoerceTable(luaContext, &markedObjects);
|
||||
HashTableDeinit(&markedObjects);
|
||||
return value;
|
||||
case LUA_TUSERDATA:
|
||||
if (!lua_getmetatable(luaContext->lua, -1)) {
|
||||
break;
|
||||
}
|
||||
luaL_getmetatable(luaContext->lua, "mSTStruct");
|
||||
if (!lua_rawequal(luaContext->lua, -1, -2)) {
|
||||
lua_pop(luaContext->lua, 2);
|
||||
break;
|
||||
lua_pop(luaContext->lua, 1);
|
||||
luaL_getmetatable(luaContext->lua, "mSTList");
|
||||
if (!lua_rawequal(luaContext->lua, -1, -2)) {
|
||||
lua_pop(luaContext->lua, 1);
|
||||
luaL_getmetatable(luaContext->lua, "mSTTable");
|
||||
if (!lua_rawequal(luaContext->lua, -1, -2)) {
|
||||
lua_pop(luaContext->lua, 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_pop(luaContext->lua, 2);
|
||||
value = lua_touserdata(luaContext->lua, -1);
|
||||
value = mScriptContextAccessWeakref(luaContext->d.context, value);
|
||||
if (value->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
value = mScriptValueUnwrap(value);
|
||||
}
|
||||
if (value) {
|
||||
mScriptValueRef(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (pop) {
|
||||
|
@ -698,6 +720,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v
|
|||
lua_pushnil(luaContext->lua);
|
||||
return true;
|
||||
}
|
||||
mScriptContextFillPool(luaContext->d.context, value);
|
||||
}
|
||||
struct mScriptValue derefPtr;
|
||||
if (value->type->base == mSCRIPT_TYPE_OPAQUE) {
|
||||
|
@ -788,6 +811,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v
|
|||
if (needsWeakref) {
|
||||
*newValue = mSCRIPT_MAKE(WEAKREF, weakref);
|
||||
} else {
|
||||
mScriptValueRef(value);
|
||||
mScriptValueWrap(value, newValue);
|
||||
}
|
||||
lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTList");
|
||||
|
@ -798,6 +822,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v
|
|||
if (needsWeakref) {
|
||||
*newValue = mSCRIPT_MAKE(WEAKREF, weakref);
|
||||
} else {
|
||||
mScriptValueRef(value);
|
||||
mScriptValueWrap(value, newValue);
|
||||
}
|
||||
lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTTable");
|
||||
|
@ -809,7 +834,6 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v
|
|||
newValue->refs = mSCRIPT_VALUE_UNREF;
|
||||
newValue->type->alloc(newValue);
|
||||
lua_pushcclosure(luaContext->lua, _luaThunk, 1);
|
||||
mScriptValueDeref(value);
|
||||
break;
|
||||
case mSCRIPT_TYPE_OBJECT:
|
||||
if (!value->value.opaque) {
|
||||
|
@ -820,6 +844,7 @@ bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* v
|
|||
if (needsWeakref) {
|
||||
*newValue = mSCRIPT_MAKE(WEAKREF, weakref);
|
||||
} else {
|
||||
mScriptValueRef(value);
|
||||
mScriptValueWrap(value, newValue);
|
||||
}
|
||||
lua_getfield(luaContext->lua, LUA_REGISTRYINDEX, "mSTStruct");
|
||||
|
@ -920,16 +945,12 @@ const char* _luaGetError(struct mScriptEngineContext* context) {
|
|||
return luaContext->lastError;
|
||||
}
|
||||
|
||||
bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList* frame, bool internal) {
|
||||
bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList* frame) {
|
||||
bool ok = true;
|
||||
if (frame) {
|
||||
size_t i;
|
||||
for (i = 0; i < mScriptListSize(frame); ++i) {
|
||||
struct mScriptValue* value = mScriptListGetPointer(frame, i);
|
||||
if (internal && value->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
value = mScriptValueUnwrap(value);
|
||||
mScriptContextFillPool(luaContext->d.context, value);
|
||||
}
|
||||
if (!_luaWrap(luaContext, value)) {
|
||||
ok = false;
|
||||
break;
|
||||
|
@ -953,8 +974,11 @@ bool _luaPopFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList
|
|||
ok = false;
|
||||
break;
|
||||
}
|
||||
mScriptValueWrap(value, mScriptListAppend(frame));
|
||||
mScriptValueDeref(value);
|
||||
struct mScriptValue* tail = mScriptListAppend(frame);
|
||||
mScriptValueWrap(value, tail);
|
||||
if (tail->type == value->type) {
|
||||
mScriptValueDeref(value);
|
||||
}
|
||||
}
|
||||
if (count > i) {
|
||||
lua_pop(luaContext->lua, count - i);
|
||||
|
@ -981,6 +1005,26 @@ bool _luaCall(struct mScriptFrame* frame, void* context) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void _freeFrame(struct mScriptList* frame) {
|
||||
size_t i;
|
||||
for (i = 0; i < mScriptListSize(frame); ++i) {
|
||||
struct mScriptValue* val = mScriptValueUnwrap(mScriptListGetPointer(frame, i));
|
||||
if (val) {
|
||||
mScriptValueDeref(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _autofreeFrame(struct mScriptContext* context, struct mScriptList* frame) {
|
||||
size_t i;
|
||||
for (i = 0; i < mScriptListSize(frame); ++i) {
|
||||
struct mScriptValue* val = mScriptValueUnwrap(mScriptListGetPointer(frame, i));
|
||||
if (val) {
|
||||
mScriptContextFillPool(context, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* frame) {
|
||||
int nargs = 0;
|
||||
if (frame) {
|
||||
|
@ -992,7 +1036,7 @@ bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame*
|
|||
luaContext->lastError = NULL;
|
||||
}
|
||||
|
||||
if (frame && !_luaPushFrame(luaContext, &frame->arguments, false)) {
|
||||
if (frame && !_luaPushFrame(luaContext, &frame->arguments)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1057,6 +1101,7 @@ int _luaThunk(lua_State* lua) {
|
|||
struct mScriptFrame frame;
|
||||
mScriptFrameInit(&frame);
|
||||
if (!_luaPopFrame(luaContext, &frame.arguments)) {
|
||||
_freeFrame(&frame.arguments);
|
||||
mScriptContextDrainPool(luaContext->d.context);
|
||||
mScriptFrameDeinit(&frame);
|
||||
luaL_traceback(lua, lua, "Error calling function (translating arguments into runtime)", 1);
|
||||
|
@ -1064,19 +1109,21 @@ int _luaThunk(lua_State* lua) {
|
|||
}
|
||||
|
||||
struct mScriptValue* fn = lua_touserdata(lua, lua_upvalueindex(1));
|
||||
_autofreeFrame(luaContext->d.context, &frame.arguments);
|
||||
if (!fn || !mScriptInvoke(fn, &frame)) {
|
||||
mScriptContextDrainPool(luaContext->d.context);
|
||||
mScriptFrameDeinit(&frame);
|
||||
luaL_traceback(lua, lua, "Error calling function (invoking failed)", 1);
|
||||
return lua_error(lua);
|
||||
}
|
||||
|
||||
if (!_luaPushFrame(luaContext, &frame.returnValues, true)) {
|
||||
mScriptFrameDeinit(&frame);
|
||||
bool ok = _luaPushFrame(luaContext, &frame.returnValues);
|
||||
mScriptContextDrainPool(luaContext->d.context);
|
||||
mScriptFrameDeinit(&frame);
|
||||
if (!ok) {
|
||||
luaL_traceback(lua, lua, "Error calling function (translating return values from runtime)", 1);
|
||||
return lua_error(lua);
|
||||
}
|
||||
mScriptContextDrainPool(luaContext->d.context);
|
||||
mScriptFrameDeinit(&frame);
|
||||
|
||||
return lua_gettop(luaContext->lua);
|
||||
}
|
||||
|
@ -1131,19 +1178,22 @@ int _luaSetObject(lua_State* lua) {
|
|||
strlcpy(key, keyPtr, sizeof(key));
|
||||
lua_pop(lua, 2);
|
||||
|
||||
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
|
||||
if (!obj) {
|
||||
luaL_traceback(lua, lua, "Invalid object", 1);
|
||||
return lua_error(lua);
|
||||
}
|
||||
|
||||
if (!val) {
|
||||
luaL_traceback(lua, lua, "Error translating value to runtime", 1);
|
||||
return lua_error(lua);
|
||||
}
|
||||
|
||||
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
|
||||
if (!obj) {
|
||||
mScriptValueDeref(val);
|
||||
mScriptContextDrainPool(luaContext->d.context);
|
||||
luaL_traceback(lua, lua, "Invalid object", 1);
|
||||
return lua_error(lua);
|
||||
}
|
||||
|
||||
if (!mScriptObjectSet(obj, key, val)) {
|
||||
mScriptValueDeref(val);
|
||||
mScriptContextDrainPool(luaContext->d.context);
|
||||
char error[MAX_KEY_SIZE + 16];
|
||||
snprintf(error, sizeof(error), "Invalid key '%s'", key);
|
||||
luaL_traceback(lua, lua, "Invalid key", 1);
|
||||
|
@ -1187,8 +1237,11 @@ int _luaGetTable(lua_State* lua) {
|
|||
}
|
||||
lua_pop(lua, 2);
|
||||
|
||||
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
|
||||
if (!obj) {
|
||||
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
|
||||
if (obj->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
obj = mScriptValueUnwrap(obj);
|
||||
}
|
||||
if (!obj || obj->type != mSCRIPT_TYPE_MS_TABLE) {
|
||||
luaL_traceback(lua, lua, "Invalid table", 1);
|
||||
return lua_error(lua);
|
||||
}
|
||||
|
@ -1220,7 +1273,10 @@ int _luaLenTable(lua_State* lua) {
|
|||
lua_pop(lua, 1);
|
||||
|
||||
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
|
||||
if (!obj) {
|
||||
if (obj->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
obj = mScriptValueUnwrap(obj);
|
||||
}
|
||||
if (!obj || obj->type != mSCRIPT_TYPE_MS_TABLE) {
|
||||
luaL_traceback(lua, lua, "Invalid table", 1);
|
||||
return lua_error(lua);
|
||||
}
|
||||
|
@ -1256,7 +1312,10 @@ static int _luaNextTable(lua_State* lua) {
|
|||
lua_pop(lua, 2);
|
||||
|
||||
table = mScriptContextAccessWeakref(luaContext->d.context, table);
|
||||
if (!table) {
|
||||
if (table->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
table = mScriptValueUnwrap(table);
|
||||
}
|
||||
if (!table || table->type != mSCRIPT_TYPE_MS_TABLE) {
|
||||
luaL_traceback(lua, lua, "Invalid table", 1);
|
||||
return lua_error(lua);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ static const struct _mScriptSocketErrorMapping {
|
|||
#ifndef USE_GETHOSTBYNAME
|
||||
#ifdef _WIN32
|
||||
{ WSATRY_AGAIN, mSCRIPT_SOCKERR_AGAIN },
|
||||
{ WSAEWOULDBLOCK, mSCRIPT_SOCKERR_AGAIN },
|
||||
{ WSANO_RECOVERY, mSCRIPT_SOCKERR_FAILED },
|
||||
{ WSANO_DATA, mSCRIPT_SOCKERR_NO_DATA },
|
||||
{ WSAHOST_NOT_FOUND, mSCRIPT_SOCKERR_NOT_FOUND },
|
||||
|
|
|
@ -27,7 +27,14 @@ static uint32_t _mScriptCallbackAdd(struct mScriptCallbackManager* adapter, stru
|
|||
fn = mScriptValueUnwrap(fn);
|
||||
}
|
||||
uint32_t id = mScriptContextAddCallback(adapter->context, name->buffer, fn);
|
||||
mScriptValueDeref(fn);
|
||||
return id;
|
||||
}
|
||||
|
||||
static uint32_t _mScriptCallbackOneshot(struct mScriptCallbackManager* adapter, struct mScriptString* name, struct mScriptValue* fn) {
|
||||
if (fn->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
fn = mScriptValueUnwrap(fn);
|
||||
}
|
||||
uint32_t id = mScriptContextAddOneshot(adapter->context, name->buffer, fn);
|
||||
return id;
|
||||
}
|
||||
|
||||
|
@ -37,6 +44,7 @@ static void _mScriptCallbackRemove(struct mScriptCallbackManager* adapter, uint3
|
|||
|
||||
mSCRIPT_DECLARE_STRUCT(mScriptCallbackManager);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCallbackManager, U32, add, _mScriptCallbackAdd, 2, STR, callback, WRAPPER, function);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCallbackManager, U32, oneshot, _mScriptCallbackOneshot, 2, STR, callback, WRAPPER, function);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCallbackManager, remove, _mScriptCallbackRemove, 1, U32, cbid);
|
||||
|
||||
static uint64_t mScriptMakeBitmask(struct mScriptList* list) {
|
||||
|
@ -87,6 +95,8 @@ mSCRIPT_DEFINE_STRUCT(mScriptCallbackManager)
|
|||
)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Add a callback of the named type. The returned id can be used to remove it later")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, add)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Add a one-shot callback of the named type that will be automatically removed after called. The returned id can be used to remove it early")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, oneshot)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Remove a callback with the previously retuned id")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, remove)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
|
|
@ -0,0 +1,551 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/script/storage.h>
|
||||
|
||||
#include <mgba/core/config.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#include <json.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define STORAGE_LEN_MAX 64
|
||||
|
||||
struct mScriptStorageBucket {
|
||||
char* name;
|
||||
struct mScriptValue* root;
|
||||
bool autoflush;
|
||||
bool dirty;
|
||||
};
|
||||
|
||||
struct mScriptStorageContext {
|
||||
struct Table buckets;
|
||||
};
|
||||
|
||||
void mScriptStorageBucketDeinit(void*);
|
||||
struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key);
|
||||
static void mScriptStorageBucketSet(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value);
|
||||
static void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value);
|
||||
static void mScriptStorageBucketSetSInt(struct mScriptStorageBucket* bucket, const char* key, int64_t value);
|
||||
static void mScriptStorageBucketSetUInt(struct mScriptStorageBucket* bucket, const char* key, uint64_t value);
|
||||
static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* bucket, const char* key, double value);
|
||||
static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* bucket, const char* key, bool value);
|
||||
static bool mScriptStorageBucketReload(struct mScriptStorageBucket* bucket);
|
||||
static bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket);
|
||||
static void mScriptStorageBucketEnableAutoFlush(struct mScriptStorageBucket* bucket, bool enable);
|
||||
|
||||
static void mScriptStorageContextDeinit(struct mScriptStorageContext*);
|
||||
static void mScriptStorageContextFlushAll(struct mScriptStorageContext*);
|
||||
struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name);
|
||||
|
||||
static bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out);
|
||||
static struct mScriptValue* mScriptStorageFromJson(struct json_object* json);
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT(mScriptStorageBucket);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, WRAPPER, _get, mScriptStorageBucketGet, 1, CHARP, key);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setSInt, mScriptStorageBucketSetSInt, 2, CHARP, key, S64, value);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setUInt, mScriptStorageBucketSetUInt, 2, CHARP, key, U64, value);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setFloat, mScriptStorageBucketSetFloat, 2, CHARP, key, F64, value);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setBool, mScriptStorageBucketSetBool, 2, CHARP, key, BOOL, value);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setStr, mScriptStorageBucketSet, 2, CHARP, key, WSTR, value);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setList, mScriptStorageBucketSet, 2, CHARP, key, WLIST, value);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setTable, mScriptStorageBucketSet, 2, CHARP, key, WTABLE, value);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setVoid, mScriptStorageBucketSetVoid, 2, CHARP, key, NUL, value);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, reload, mScriptStorageBucketReload, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, BOOL, flush, mScriptStorageBucketFlush, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, enableAutoFlush, mScriptStorageBucketEnableAutoFlush, 1, BOOL, enable);
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket)
|
||||
mSCRIPT_DEFINE_CLASS_DOCSTRING(
|
||||
"A single 'bucket' of stored data, appropriate for a single script to store its data. "
|
||||
"Fields can be set directly on the bucket objct, e.g. if you want to store a value called "
|
||||
"`foo` on a bucket named `bucket`, you can directly assign to it as `bucket.foo = value`, "
|
||||
"and retrieve it in the same way later. Primitive types (numbers, strings, lists and tables) "
|
||||
"can be stored in buckets, but complex data types (e.g. a bucket itself) cannot. Data "
|
||||
"stored in a bucket is periodically flushed to disk and persists between sessions."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setSInt)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setUInt)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setFloat)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setBool)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setStr)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setList)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setTable)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setVoid)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptStorageBucket)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Reload the state of the bucket from disk")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, reload)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Flush the bucket to disk manually")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, flush)
|
||||
mSCRIPT_DEFINE_DOCSTRING(
|
||||
"Enable or disable the automatic flushing of this bucket. This is good for ensuring buckets "
|
||||
"don't get flushed in an inconsistent state. It will also disable flushing to disk when the "
|
||||
"emulator is shut down, so make sure to either manually flush the bucket or re-enable "
|
||||
"automatic flushing whenever you're done updating it if you do disable it prior."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageBucket, enableAutoFlush)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT(mScriptStorageContext);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, _deinit, mScriptStorageContextDeinit, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageContext, S(mScriptStorageBucket), getBucket, mScriptStorageGetBucket, 1, CHARP, key);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, flushAll, mScriptStorageContextFlushAll, 0);
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(mScriptStorageContext)
|
||||
mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptStorageContext)
|
||||
mSCRIPT_DEFINE_DOCSTRING(
|
||||
"Get a bucket with the given name. Names can contain letters, numbers, "
|
||||
"underscores and periods. If a given bucket doesn't exist, it is created."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, getBucket)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Flush all buckets to disk manually")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, flushAll)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
||||
struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key) {
|
||||
struct mScriptValue* val = mScriptTableLookup(bucket->root, &mSCRIPT_MAKE_CHARP(key));
|
||||
if (val) {
|
||||
mScriptValueRef(val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void mScriptStorageBucketSet(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value) {
|
||||
struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key);
|
||||
if (value->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
value = mScriptValueUnwrap(value);
|
||||
}
|
||||
mScriptTableInsert(bucket->root, vkey, value);
|
||||
mScriptValueDeref(vkey);
|
||||
bucket->dirty = true;
|
||||
}
|
||||
|
||||
void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value) {
|
||||
UNUSED(value);
|
||||
struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key);
|
||||
mScriptTableInsert(bucket->root, vkey, &mScriptValueNull);
|
||||
mScriptValueDeref(vkey);
|
||||
bucket->dirty = true;
|
||||
}
|
||||
|
||||
#define MAKE_SCALAR_SETTER(NAME, TYPE) \
|
||||
void mScriptStorageBucketSet ## NAME (struct mScriptStorageBucket* bucket, const char* key, mSCRIPT_TYPE_C_ ## TYPE value) { \
|
||||
struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); \
|
||||
struct mScriptValue* vval = mScriptValueAlloc(mSCRIPT_TYPE_MS_ ## TYPE); \
|
||||
vval->value.mSCRIPT_TYPE_FIELD_ ## TYPE = value; \
|
||||
mScriptTableInsert(bucket->root, vkey, vval); \
|
||||
mScriptValueDeref(vkey); \
|
||||
mScriptValueDeref(vval); \
|
||||
bucket->dirty = true; \
|
||||
}
|
||||
|
||||
MAKE_SCALAR_SETTER(SInt, S64)
|
||||
MAKE_SCALAR_SETTER(UInt, U64)
|
||||
MAKE_SCALAR_SETTER(Float, F64)
|
||||
MAKE_SCALAR_SETTER(Bool, BOOL)
|
||||
|
||||
void mScriptStorageGetBucketPath(const char* bucket, char* out) {
|
||||
mCoreConfigDirectory(out, PATH_MAX);
|
||||
|
||||
strncat(out, PATH_SEP "storage" PATH_SEP, PATH_MAX);
|
||||
#ifdef _WIN32
|
||||
// TODO: Move this to vfs somewhere
|
||||
WCHAR wout[MAX_PATH];
|
||||
MultiByteToWideChar(CP_UTF8, 0, out, -1, wout, MAX_PATH);
|
||||
CreateDirectoryW(wout, NULL);
|
||||
#else
|
||||
mkdir(out, 0755);
|
||||
#endif
|
||||
|
||||
char suffix[STORAGE_LEN_MAX + 6];
|
||||
snprintf(suffix, sizeof(suffix), "%s.json", bucket);
|
||||
strncat(out, suffix, PATH_MAX);
|
||||
}
|
||||
|
||||
static struct json_object* _tableToJson(struct mScriptValue* rootVal) {
|
||||
bool ok = true;
|
||||
|
||||
struct TableIterator iter;
|
||||
struct json_object* rootObj = json_object_new_object();
|
||||
if (mScriptTableIteratorStart(rootVal, &iter)) {
|
||||
do {
|
||||
struct mScriptValue* key = mScriptTableIteratorGetKey(rootVal, &iter);
|
||||
struct mScriptValue* value = mScriptTableIteratorGetValue(rootVal, &iter);
|
||||
const char* ckey;
|
||||
if (key->type == mSCRIPT_TYPE_MS_CHARP) {
|
||||
ckey = key->value.copaque;
|
||||
} else if (key->type == mSCRIPT_TYPE_MS_STR) {
|
||||
ckey = key->value.string->buffer;
|
||||
} else {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
struct json_object* obj;
|
||||
ok = mScriptStorageToJson(value, &obj);
|
||||
|
||||
if (ok) {
|
||||
#if JSON_C_VERSION_NUM >= (13 << 8)
|
||||
ok = json_object_object_add(rootObj, ckey, obj) >= 0;
|
||||
#else
|
||||
json_object_object_add(rootObj, ckey, obj);
|
||||
#endif
|
||||
}
|
||||
} while (mScriptTableIteratorNext(rootVal, &iter) && ok);
|
||||
}
|
||||
if (!ok) {
|
||||
json_object_put(rootObj);
|
||||
return NULL;
|
||||
}
|
||||
return rootObj;
|
||||
}
|
||||
|
||||
bool mScriptStorageToJson(struct mScriptValue* value, struct json_object** out) {
|
||||
if (value->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
value = mScriptValueUnwrap(value);
|
||||
}
|
||||
|
||||
size_t i;
|
||||
bool ok = true;
|
||||
struct json_object* obj = NULL;
|
||||
switch (value->type->base) {
|
||||
case mSCRIPT_TYPE_SINT:
|
||||
obj = json_object_new_int64(value->value.s64);
|
||||
break;
|
||||
case mSCRIPT_TYPE_UINT:
|
||||
if (value->type == mSCRIPT_TYPE_MS_BOOL) {
|
||||
obj = json_object_new_boolean(value->value.u32);
|
||||
break;
|
||||
}
|
||||
#if JSON_C_VERSION_NUM >= (14 << 8)
|
||||
obj = json_object_new_uint64(value->value.u64);
|
||||
#else
|
||||
if (value->value.u64 < (uint64_t) INT64_MAX) {
|
||||
obj = json_object_new_int64(value->value.u64);
|
||||
} else {
|
||||
obj = json_object_new_double(value->value.u64);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case mSCRIPT_TYPE_FLOAT:
|
||||
obj = json_object_new_double(value->value.f64);
|
||||
break;
|
||||
case mSCRIPT_TYPE_STRING:
|
||||
obj = json_object_new_string_len(value->value.string->buffer, value->value.string->size);
|
||||
break;
|
||||
case mSCRIPT_TYPE_LIST:
|
||||
#if JSON_C_VERSION_NUM >= (15 << 8)
|
||||
obj = json_object_new_array_ext(mScriptListSize(value->value.list));
|
||||
#else
|
||||
obj = json_object_new_array();
|
||||
#endif
|
||||
for (i = 0; i < mScriptListSize(value->value.list); ++i) {
|
||||
struct json_object* listObj;
|
||||
ok = mScriptStorageToJson(mScriptListGetPointer(value->value.list, i), &listObj);
|
||||
if (!ok) {
|
||||
break;
|
||||
}
|
||||
json_object_array_add(obj, listObj);
|
||||
}
|
||||
break;
|
||||
case mSCRIPT_TYPE_TABLE:
|
||||
obj = _tableToJson(value);
|
||||
if (!obj) {
|
||||
ok = false;
|
||||
}
|
||||
break;
|
||||
case mSCRIPT_TYPE_VOID:
|
||||
obj = NULL;
|
||||
break;
|
||||
default:
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
if (obj) {
|
||||
json_object_put(obj);
|
||||
}
|
||||
*out = NULL;
|
||||
} else {
|
||||
*out = obj;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
#ifndef JSON_C_TO_STRING_PRETTY_TAB
|
||||
#define JSON_C_TO_STRING_PRETTY_TAB 0
|
||||
#endif
|
||||
|
||||
static bool _mScriptStorageBucketFlushVF(struct mScriptStorageBucket* bucket, struct VFile* vf) {
|
||||
struct json_object* rootObj;
|
||||
bool ok = mScriptStorageToJson(bucket->root, &rootObj);
|
||||
if (!ok) {
|
||||
vf->close(vf);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* json = json_object_to_json_string_ext(rootObj, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_PRETTY_TAB);
|
||||
if (!json) {
|
||||
json_object_put(rootObj);
|
||||
vf->close(vf);
|
||||
return false;
|
||||
}
|
||||
|
||||
vf->write(vf, json, strlen(json));
|
||||
vf->close(vf);
|
||||
|
||||
bucket->dirty = false;
|
||||
|
||||
json_object_put(rootObj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mScriptStorageBucketFlush(struct mScriptStorageBucket* bucket) {
|
||||
char path[PATH_MAX];
|
||||
mScriptStorageGetBucketPath(bucket->name, path);
|
||||
struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
return _mScriptStorageBucketFlushVF(bucket, vf);
|
||||
}
|
||||
|
||||
void mScriptStorageBucketEnableAutoFlush(struct mScriptStorageBucket* bucket, bool enable) {
|
||||
bucket->autoflush = enable;
|
||||
}
|
||||
|
||||
bool mScriptStorageSaveBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) {
|
||||
struct mScriptValue* value = mScriptContextGetGlobal(context, "storage");
|
||||
if (!value) {
|
||||
vf->close(vf);
|
||||
return false;
|
||||
}
|
||||
struct mScriptStorageContext* storage = value->value.opaque;
|
||||
struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName);
|
||||
return _mScriptStorageBucketFlushVF(bucket, vf);
|
||||
}
|
||||
|
||||
bool mScriptStorageSaveBucket(struct mScriptContext* context, const char* bucketName) {
|
||||
char path[PATH_MAX];
|
||||
mScriptStorageGetBucketPath(bucketName, path);
|
||||
struct VFile* vf = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
return mScriptStorageSaveBucketVF(context, bucketName, vf);
|
||||
}
|
||||
|
||||
struct mScriptValue* mScriptStorageFromJson(struct json_object* json) {
|
||||
enum json_type type = json_object_get_type(json);
|
||||
struct mScriptValue* value = NULL;
|
||||
switch (type) {
|
||||
case json_type_null:
|
||||
return &mScriptValueNull;
|
||||
case json_type_int:
|
||||
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S64);
|
||||
value->value.s64 = json_object_get_int64(json);
|
||||
break;
|
||||
case json_type_double:
|
||||
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_F64);
|
||||
value->value.f64 = json_object_get_double(json);
|
||||
break;
|
||||
case json_type_boolean:
|
||||
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_BOOL);
|
||||
value->value.u32 = json_object_get_boolean(json);
|
||||
break;
|
||||
case json_type_string:
|
||||
value = mScriptStringCreateFromBytes(json_object_get_string(json), json_object_get_string_len(json));
|
||||
break;
|
||||
case json_type_array:
|
||||
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST);
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < json_object_array_length(json); ++i) {
|
||||
struct mScriptValue* vval = mScriptStorageFromJson(json_object_array_get_idx(json, i));
|
||||
if (!vval) {
|
||||
mScriptValueDeref(value);
|
||||
value = NULL;
|
||||
break;
|
||||
}
|
||||
mScriptValueWrap(vval, mScriptListAppend(value->value.list));
|
||||
mScriptValueDeref(vval);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case json_type_object:
|
||||
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
|
||||
{
|
||||
json_object_object_foreach(json, jkey, jval) {
|
||||
struct mScriptValue* vval = mScriptStorageFromJson(jval);
|
||||
if (!vval) {
|
||||
mScriptValueDeref(value);
|
||||
value = NULL;
|
||||
break;
|
||||
}
|
||||
struct mScriptValue* vkey = mScriptStringCreateFromUTF8(jkey);
|
||||
mScriptTableInsert(value, vkey, vval);
|
||||
mScriptValueDeref(vkey);
|
||||
mScriptValueDeref(vval);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static struct mScriptValue* _mScriptStorageLoadJson(struct VFile* vf) {
|
||||
ssize_t size = vf->size(vf);
|
||||
if (size < 2) {
|
||||
vf->close(vf);
|
||||
return NULL;
|
||||
}
|
||||
char* json = calloc(1, size + 1);
|
||||
if (vf->read(vf, json, size) != size) {
|
||||
vf->close(vf);
|
||||
return NULL;
|
||||
}
|
||||
vf->close(vf);
|
||||
|
||||
struct json_object* obj = json_tokener_parse(json);
|
||||
free(json);
|
||||
if (!obj) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mScriptValue* root = mScriptStorageFromJson(obj);
|
||||
json_object_put(obj);
|
||||
return root;
|
||||
}
|
||||
|
||||
bool mScriptStorageBucketReload(struct mScriptStorageBucket* bucket) {
|
||||
char path[PATH_MAX];
|
||||
mScriptStorageGetBucketPath(bucket->name, path);
|
||||
struct VFile* vf = VFileOpen(path, O_RDONLY);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
struct mScriptValue* root = _mScriptStorageLoadJson(vf);
|
||||
if (!root) {
|
||||
return false;
|
||||
}
|
||||
if (bucket->root) {
|
||||
mScriptValueDeref(bucket->root);
|
||||
}
|
||||
bucket->root = root;
|
||||
|
||||
bucket->dirty = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mScriptStorageLoadBucketVF(struct mScriptContext* context, const char* bucketName, struct VFile* vf) {
|
||||
struct mScriptValue* value = mScriptContextGetGlobal(context, "storage");
|
||||
if (!value) {
|
||||
vf->close(vf);
|
||||
return false;
|
||||
}
|
||||
struct mScriptStorageContext* storage = value->value.opaque;
|
||||
struct mScriptValue* root = _mScriptStorageLoadJson(vf);
|
||||
if (!root) {
|
||||
return false;
|
||||
}
|
||||
struct mScriptStorageBucket* bucket = mScriptStorageGetBucket(storage, bucketName);
|
||||
mScriptValueDeref(bucket->root);
|
||||
bucket->root = root;
|
||||
|
||||
bucket->dirty = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mScriptStorageLoadBucket(struct mScriptContext* context, const char* bucketName) {
|
||||
char path[PATH_MAX];
|
||||
mScriptStorageGetBucketPath(bucketName, path);
|
||||
struct VFile* vf = VFileOpen(path, O_RDONLY);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
return mScriptStorageLoadBucketVF(context, bucketName, vf);
|
||||
}
|
||||
|
||||
void mScriptContextAttachStorage(struct mScriptContext* context) {
|
||||
struct mScriptStorageContext* storage = calloc(1, sizeof(*storage));
|
||||
struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptStorageContext));
|
||||
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
|
||||
value->value.opaque = storage;
|
||||
|
||||
HashTableInit(&storage->buckets, 0, mScriptStorageBucketDeinit);
|
||||
|
||||
mScriptContextSetGlobal(context, "storage", value);
|
||||
mScriptContextSetDocstring(context, "storage", "Singleton instance of struct::mScriptStorageContext");
|
||||
}
|
||||
|
||||
void mScriptStorageFlushAll(struct mScriptContext* context) {
|
||||
struct mScriptValue* value = mScriptContextGetGlobal(context, "storage");
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
struct mScriptStorageContext* storage = value->value.opaque;
|
||||
mScriptStorageContextFlushAll(storage);
|
||||
}
|
||||
|
||||
void mScriptStorageContextDeinit(struct mScriptStorageContext* storage) {
|
||||
HashTableDeinit(&storage->buckets);
|
||||
}
|
||||
|
||||
void mScriptStorageContextFlushAll(struct mScriptStorageContext* storage) {
|
||||
struct TableIterator iter;
|
||||
if (HashTableIteratorStart(&storage->buckets, &iter)) {
|
||||
do {
|
||||
struct mScriptStorageBucket* bucket = HashTableIteratorGetValue(&storage->buckets, &iter);
|
||||
if (bucket->autoflush) {
|
||||
mScriptStorageBucketFlush(bucket);
|
||||
}
|
||||
} while (HashTableIteratorNext(&storage->buckets, &iter));
|
||||
}
|
||||
}
|
||||
|
||||
struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext* storage, const char* name) {
|
||||
if (!name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Check if name is allowed
|
||||
// Currently only names matching /[0-9A-Za-z][0-9A-Za-z_.]*/ are allowed
|
||||
size_t i;
|
||||
for (i = 0; name[i]; ++i) {
|
||||
if (i >= STORAGE_LEN_MAX) {
|
||||
return NULL;
|
||||
}
|
||||
if (isalnum(name[i])) {
|
||||
continue;
|
||||
}
|
||||
if (name[i] == '_') {
|
||||
continue;
|
||||
}
|
||||
if (i > 0 && name[i] == '.') {
|
||||
continue;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
struct mScriptStorageBucket* bucket = HashTableLookup(&storage->buckets, name);
|
||||
if (bucket) {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
bucket = calloc(1, sizeof(*bucket));
|
||||
bucket->name = strdup(name);
|
||||
bucket->autoflush = true;
|
||||
if (!mScriptStorageBucketReload(bucket)) {
|
||||
bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
|
||||
}
|
||||
HashTableInsert(&storage->buckets, name, bucket);
|
||||
return bucket;
|
||||
}
|
||||
|
||||
void mScriptStorageBucketDeinit(void* data) {
|
||||
struct mScriptStorageBucket* bucket = data;
|
||||
if (bucket->dirty) {
|
||||
mScriptStorageBucketFlush(bucket);
|
||||
}
|
||||
mScriptValueDeref(bucket->root);
|
||||
free(bucket->name);
|
||||
free(bucket);
|
||||
}
|
|
@ -46,6 +46,14 @@ struct TestF {
|
|||
int* ref;
|
||||
};
|
||||
|
||||
struct TestG {
|
||||
const char* name;
|
||||
int64_t s;
|
||||
uint64_t u;
|
||||
double f;
|
||||
const char* c;
|
||||
};
|
||||
|
||||
static int32_t testAi0(struct TestA* a) {
|
||||
return a->i;
|
||||
}
|
||||
|
@ -83,6 +91,26 @@ static void testDeinit(struct TestF* f) {
|
|||
++*f->ref;
|
||||
}
|
||||
|
||||
static void testSetS(struct TestG* g, const char* name, int64_t val) {
|
||||
g->name = name;
|
||||
g->s = val;
|
||||
}
|
||||
|
||||
static void testSetU(struct TestG* g, const char* name, uint64_t val) {
|
||||
g->name = name;
|
||||
g->u = val;
|
||||
}
|
||||
|
||||
static void testSetF(struct TestG* g, const char* name, double val) {
|
||||
g->name = name;
|
||||
g->f = val;
|
||||
}
|
||||
|
||||
static void testSetC(struct TestG* g, const char* name, const char* val) {
|
||||
g->name = name;
|
||||
g->c = val;
|
||||
}
|
||||
|
||||
#define MEMBER_A_DOCSTRING "Member a"
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT(TestA);
|
||||
|
@ -157,6 +185,19 @@ mSCRIPT_DEFINE_STRUCT(TestF)
|
|||
mSCRIPT_DEFINE_STRUCT_DEINIT(TestF)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT(TestG);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setS, testSetS, 2, CHARP, name, S64, value);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setU, testSetU, 2, CHARP, name, U64, value);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setF, testSetF, 2, CHARP, name, F64, value);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestG, setC, testSetC, 2, CHARP, name, CHARP, value);
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(TestG)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setS)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setU)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setF)
|
||||
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TestG, setC)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
||||
M_TEST_DEFINE(testALayout) {
|
||||
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls;
|
||||
assert_false(cls->init);
|
||||
|
@ -1032,6 +1073,67 @@ M_TEST_DEFINE(testFDeinit) {
|
|||
assert_false(cls->init);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(testGSet) {
|
||||
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestG)->details.cls;
|
||||
|
||||
struct TestG s = {
|
||||
};
|
||||
|
||||
assert_int_equal(s.s, 0);
|
||||
assert_int_equal(s.u, 0);
|
||||
assert_float_equal(s.f, 0, 0);
|
||||
assert_null(s.c);
|
||||
|
||||
struct mScriptValue sval = mSCRIPT_MAKE_S(TestG, &s);
|
||||
struct mScriptValue val;
|
||||
struct mScriptValue* pval;
|
||||
|
||||
val = mSCRIPT_MAKE_S64(1);
|
||||
assert_true(mScriptObjectSet(&sval, "a", &val));
|
||||
assert_int_equal(s.s, 1);
|
||||
assert_string_equal(s.name, "a");
|
||||
|
||||
val = mSCRIPT_MAKE_U64(2);
|
||||
assert_true(mScriptObjectSet(&sval, "b", &val));
|
||||
assert_int_equal(s.u, 2);
|
||||
assert_string_equal(s.name, "b");
|
||||
|
||||
val = mSCRIPT_MAKE_F64(1.5);
|
||||
assert_true(mScriptObjectSet(&sval, "c", &val));
|
||||
assert_float_equal(s.f, 1.5, 0);
|
||||
assert_string_equal(s.name, "c");
|
||||
|
||||
val = mSCRIPT_MAKE_CHARP("hello");
|
||||
assert_true(mScriptObjectSet(&sval, "d", &val));
|
||||
assert_string_equal(s.c, "hello");
|
||||
assert_string_equal(s.name, "d");
|
||||
|
||||
val = mSCRIPT_MAKE_S32(3);
|
||||
assert_true(mScriptObjectSet(&sval, "a", &val));
|
||||
assert_int_equal(s.s, 3);
|
||||
|
||||
val = mSCRIPT_MAKE_S16(4);
|
||||
assert_true(mScriptObjectSet(&sval, "a", &val));
|
||||
assert_int_equal(s.s, 4);
|
||||
|
||||
val = mSCRIPT_MAKE_S8(5);
|
||||
assert_true(mScriptObjectSet(&sval, "a", &val));
|
||||
assert_int_equal(s.s, 5);
|
||||
|
||||
val = mSCRIPT_MAKE_BOOL(false);
|
||||
assert_true(mScriptObjectSet(&sval, "a", &val));
|
||||
assert_int_equal(s.u, 0);
|
||||
|
||||
pval = mScriptStringCreateFromASCII("goodbye");
|
||||
assert_true(mScriptObjectSet(&sval, "a", pval));
|
||||
assert_string_equal(s.c, "goodbye");
|
||||
mScriptValueDeref(pval);
|
||||
|
||||
assert_true(cls->init);
|
||||
mScriptClassDeinit(cls);
|
||||
assert_false(cls->init);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE(mScriptClasses,
|
||||
cmocka_unit_test(testALayout),
|
||||
cmocka_unit_test(testASignatures),
|
||||
|
@ -1047,4 +1149,5 @@ M_TEST_SUITE_DEFINE(mScriptClasses,
|
|||
cmocka_unit_test(testDSet),
|
||||
cmocka_unit_test(testEGet),
|
||||
cmocka_unit_test(testFDeinit),
|
||||
cmocka_unit_test(testGSet),
|
||||
)
|
||||
|
|
|
@ -66,9 +66,14 @@ static int32_t sum(struct mScriptList* list) {
|
|||
return sum;
|
||||
}
|
||||
|
||||
static unsigned tableSize(struct Table* table) {
|
||||
return TableSize(table);
|
||||
}
|
||||
|
||||
mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, a);
|
||||
mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, a, S32, b);
|
||||
mSCRIPT_BIND_FUNCTION(boundSum, S32, sum, 1, LIST, list);
|
||||
mSCRIPT_BIND_FUNCTION(boundTableSize, U32, tableSize, 1, TABLE, table);
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT(Test);
|
||||
mSCRIPT_DECLARE_STRUCT_D_METHOD(Test, S32, ifn0, 0);
|
||||
|
@ -371,13 +376,51 @@ M_TEST_DEFINE(callCFunc) {
|
|||
assert_true(a.type->equal(&a, val));
|
||||
mScriptValueDeref(val);
|
||||
|
||||
LOAD_PROGRAM("b('a')");
|
||||
assert_false(lua->run(lua));
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(callCTable) {
|
||||
SETUP_LUA;
|
||||
|
||||
assert_true(lua->setGlobal(lua, "b", &boundTableSize));
|
||||
|
||||
TEST_PROGRAM("assert(b({}) == 0)");
|
||||
assert_null(lua->getError(lua));
|
||||
|
||||
TEST_PROGRAM("assert(b({[2]=1}) == 1)");
|
||||
assert_null(lua->getError(lua));
|
||||
|
||||
TEST_PROGRAM("assert(b({a=1}) == 1)");
|
||||
assert_null(lua->getError(lua));
|
||||
|
||||
TEST_PROGRAM("assert(b({a={}}) == 1)");
|
||||
assert_null(lua->getError(lua));
|
||||
|
||||
LOAD_PROGRAM(
|
||||
"a = {}\n"
|
||||
"a.b = a\n"
|
||||
"assert(b(a) == 1)\n"
|
||||
);
|
||||
assert_false(lua->run(lua));
|
||||
|
||||
LOAD_PROGRAM(
|
||||
"a = {}\n"
|
||||
"a.b = {}\n"
|
||||
"a.b.c = a\n"
|
||||
"assert(b(a) == 1)\n"
|
||||
);
|
||||
assert_false(lua->run(lua));
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(globalNull) {
|
||||
SETUP_LUA;
|
||||
|
||||
struct Test s = {};
|
||||
struct mScriptValue* val;
|
||||
struct mScriptValue a;
|
||||
|
||||
LOAD_PROGRAM("assert(a)");
|
||||
|
@ -764,6 +807,48 @@ M_TEST_DEFINE(linkedList) {
|
|||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(listConvert) {
|
||||
SETUP_LUA;
|
||||
|
||||
struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST);
|
||||
|
||||
assert_true(lua->setGlobal(lua, "l", list));
|
||||
TEST_PROGRAM("assert(l)");
|
||||
|
||||
struct mScriptValue* val = lua->getGlobal(lua, "l");
|
||||
assert_non_null(val);
|
||||
if (val->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
val = mScriptValueUnwrap(val);
|
||||
}
|
||||
assert_ptr_equal(val->type, mSCRIPT_TYPE_MS_LIST);
|
||||
assert_ptr_equal(val->value.list, list->value.list);
|
||||
mScriptValueDeref(val);
|
||||
mScriptValueDeref(list);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(tableConvert) {
|
||||
SETUP_LUA;
|
||||
|
||||
struct mScriptValue* list = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
|
||||
|
||||
assert_true(lua->setGlobal(lua, "l", list));
|
||||
TEST_PROGRAM("assert(l)");
|
||||
|
||||
struct mScriptValue* val = lua->getGlobal(lua, "l");
|
||||
assert_non_null(val);
|
||||
if (val->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
val = mScriptValueUnwrap(val);
|
||||
}
|
||||
assert_ptr_equal(val->type, mSCRIPT_TYPE_MS_TABLE);
|
||||
assert_ptr_equal(val->value.table, list->value.table);
|
||||
mScriptValueDeref(val);
|
||||
mScriptValueDeref(list);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua,
|
||||
cmocka_unit_test(create),
|
||||
cmocka_unit_test(loadGood),
|
||||
|
@ -774,6 +859,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua,
|
|||
cmocka_unit_test(rootScope),
|
||||
cmocka_unit_test(callLuaFunc),
|
||||
cmocka_unit_test(callCFunc),
|
||||
cmocka_unit_test(callCTable),
|
||||
cmocka_unit_test(globalNull),
|
||||
cmocka_unit_test(globalStructFieldGet),
|
||||
cmocka_unit_test(globalStructFieldSet),
|
||||
|
@ -782,5 +868,7 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua,
|
|||
cmocka_unit_test(tableLookup),
|
||||
cmocka_unit_test(tableIterate),
|
||||
cmocka_unit_test(callList),
|
||||
cmocka_unit_test(linkedList)
|
||||
cmocka_unit_test(linkedList),
|
||||
cmocka_unit_test(listConvert),
|
||||
cmocka_unit_test(tableConvert),
|
||||
)
|
||||
|
|
|
@ -96,8 +96,41 @@ M_TEST_DEFINE(callbacks) {
|
|||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(oneshot) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM(
|
||||
"val = 0\n"
|
||||
"function cb()\n"
|
||||
" val = val + 1\n"
|
||||
"end\n"
|
||||
"id = callbacks:oneshot('test', cb)\n"
|
||||
"assert(id)"
|
||||
);
|
||||
|
||||
TEST_VALUE(S32, "val", 0);
|
||||
|
||||
mScriptContextTriggerCallback(&context, "test", NULL);
|
||||
TEST_VALUE(S32, "val", 1);
|
||||
|
||||
mScriptContextTriggerCallback(&context, "test", NULL);
|
||||
TEST_VALUE(S32, "val", 1);
|
||||
|
||||
TEST_PROGRAM(
|
||||
"id = callbacks:oneshot('test', cb)\n"
|
||||
"assert(id)\n"
|
||||
"callbacks:remove(id)"
|
||||
);
|
||||
|
||||
mScriptContextTriggerCallback(&context, "test", NULL);
|
||||
TEST_VALUE(S32, "val", 1);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStdlib,
|
||||
cmocka_unit_test(bitMask),
|
||||
cmocka_unit_test(bitUnmask),
|
||||
cmocka_unit_test(callbacks),
|
||||
cmocka_unit_test(oneshot),
|
||||
)
|
||||
|
|
|
@ -0,0 +1,585 @@
|
|||
/* Copyright (c) 2013-2023 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "util/test/suite.h"
|
||||
|
||||
#include <mgba/internal/script/lua.h>
|
||||
#include <mgba/script/storage.h>
|
||||
#include <mgba/script/types.h>
|
||||
|
||||
#include "script/test.h"
|
||||
|
||||
#define SETUP_LUA \
|
||||
struct mScriptContext context; \
|
||||
mScriptContextInit(&context); \
|
||||
struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \
|
||||
mScriptContextAttachStdlib(&context); \
|
||||
mScriptContextAttachStorage(&context); \
|
||||
char bucketPath[PATH_MAX]; \
|
||||
mScriptStorageGetBucketPath("xtest", bucketPath); \
|
||||
remove(bucketPath)
|
||||
|
||||
M_TEST_SUITE_SETUP(mScriptStorage) {
|
||||
if (mSCRIPT_ENGINE_LUA->init) {
|
||||
mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
M_TEST_SUITE_TEARDOWN(mScriptStorage) {
|
||||
if (mSCRIPT_ENGINE_LUA->deinit) {
|
||||
mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(basicInt) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
TEST_PROGRAM("bucket.a = 1");
|
||||
TEST_PROGRAM("assert(bucket.a == 1)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(basicFloat) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
TEST_PROGRAM("bucket.a = 0.5");
|
||||
TEST_PROGRAM("assert(bucket.a == 0.5)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(basicBool) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
TEST_PROGRAM("bucket.a = true");
|
||||
TEST_PROGRAM("assert(bucket.a == true)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(basicNil) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
TEST_PROGRAM("bucket.a = nil");
|
||||
TEST_PROGRAM("assert(bucket.a == nil)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(basicString) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
TEST_PROGRAM("bucket.a = 'hello'");
|
||||
TEST_PROGRAM("assert(bucket.a == 'hello')");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(basicList) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
TEST_PROGRAM("bucket.a = {1}");
|
||||
TEST_PROGRAM("assert(#bucket.a == 1)");
|
||||
TEST_PROGRAM("assert(bucket.a[1] == 1)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(basicTable) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
TEST_PROGRAM("bucket.a = {['a']=1}");
|
||||
TEST_PROGRAM("assert(#bucket.a == 1)");
|
||||
TEST_PROGRAM("assert(bucket.a.a == 1)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(nullByteString) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
TEST_PROGRAM("bucket.a = 'a\\x00b'");
|
||||
TEST_PROGRAM("assert(bucket.a == 'a\\x00b')");
|
||||
TEST_PROGRAM("assert(#bucket.a == 3)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(structured) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
TEST_PROGRAM(
|
||||
"bucket.a = {\n"
|
||||
" ['a'] = 1,\n"
|
||||
" ['b'] = {1},\n"
|
||||
" ['c'] = {\n"
|
||||
" ['d'] = 1\n"
|
||||
" }\n"
|
||||
"}"
|
||||
);
|
||||
TEST_PROGRAM("assert(bucket.a)");
|
||||
TEST_PROGRAM("assert(bucket.a.a == 1)");
|
||||
TEST_PROGRAM("assert(#bucket.a.b == 1)");
|
||||
TEST_PROGRAM("assert(bucket.a.b[1] == 1)");
|
||||
TEST_PROGRAM("assert(#bucket.a.c == 1)");
|
||||
TEST_PROGRAM("assert(bucket.a.c.d == 1)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(invalidObject) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
LOAD_PROGRAM("bucket.a = bucket");
|
||||
assert_false(lua->run(lua));
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(serializeInt) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("bucket.a = 1");
|
||||
struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf));
|
||||
vf = VFileOpen("test.json", O_RDONLY);
|
||||
assert_non_null(vf);
|
||||
ssize_t size = vf->size(vf);
|
||||
char* buf = calloc(1, size + 1);
|
||||
assert_int_equal(vf->read(vf, buf, size), size);
|
||||
assert_string_equal(buf, "{\n\t\"a\":1\n}");
|
||||
free(buf);
|
||||
vf->close(vf);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(serializeFloat) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("bucket.a = 0.5");
|
||||
struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf));
|
||||
vf = VFileOpen("test.json", O_RDONLY);
|
||||
assert_non_null(vf);
|
||||
ssize_t size = vf->size(vf);
|
||||
char* buf = calloc(1, size + 1);
|
||||
assert_int_equal(vf->read(vf, buf, size), size);
|
||||
assert_string_equal(buf, "{\n\t\"a\":0.5\n}");
|
||||
free(buf);
|
||||
vf->close(vf);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(serializeBool) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("bucket.a = true");
|
||||
struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf));
|
||||
vf = VFileOpen("test.json", O_RDONLY);
|
||||
assert_non_null(vf);
|
||||
ssize_t size = vf->size(vf);
|
||||
char* buf = calloc(1, size + 1);
|
||||
assert_int_equal(vf->read(vf, buf, size), size);
|
||||
assert_string_equal(buf, "{\n\t\"a\":true\n}");
|
||||
free(buf);
|
||||
vf->close(vf);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(serializeNil) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("bucket.a = nil");
|
||||
struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf));
|
||||
vf = VFileOpen("test.json", O_RDONLY);
|
||||
assert_non_null(vf);
|
||||
ssize_t size = vf->size(vf);
|
||||
char* buf = calloc(1, size + 1);
|
||||
assert_int_equal(vf->read(vf, buf, size), size);
|
||||
assert_string_equal(buf, "{\n\t\"a\":null\n}");
|
||||
free(buf);
|
||||
vf->close(vf);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(serializeString) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("bucket.a = 'hello'");
|
||||
struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf));
|
||||
vf = VFileOpen("test.json", O_RDONLY);
|
||||
assert_non_null(vf);
|
||||
ssize_t size = vf->size(vf);
|
||||
char* buf = calloc(1, size + 1);
|
||||
assert_int_equal(vf->read(vf, buf, size), size);
|
||||
assert_string_equal(buf, "{\n\t\"a\":\"hello\"\n}");
|
||||
free(buf);
|
||||
vf->close(vf);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(serializeList) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("bucket.a = {1, 2}");
|
||||
struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf));
|
||||
vf = VFileOpen("test.json", O_RDONLY);
|
||||
assert_non_null(vf);
|
||||
ssize_t size = vf->size(vf);
|
||||
char* buf = calloc(1, size + 1);
|
||||
assert_int_equal(vf->read(vf, buf, size), size);
|
||||
assert_string_equal(buf, "{\n\t\"a\":[\n\t\t1,\n\t\t2\n\t]\n}");
|
||||
free(buf);
|
||||
vf->close(vf);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(serializeTable) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("bucket.a = {['b']=1}");
|
||||
struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf));
|
||||
vf = VFileOpen("test.json", O_RDONLY);
|
||||
assert_non_null(vf);
|
||||
ssize_t size = vf->size(vf);
|
||||
char* buf = calloc(1, size + 1);
|
||||
assert_int_equal(vf->read(vf, buf, size), size);
|
||||
assert_string_equal(buf, "{\n\t\"a\":{\n\t\t\"b\":1\n\t}\n}");
|
||||
free(buf);
|
||||
vf->close(vf);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(serializeNullByteString) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("bucket.a = 'a\\x00b'");
|
||||
struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf));
|
||||
vf = VFileOpen("test.json", O_RDONLY);
|
||||
assert_non_null(vf);
|
||||
ssize_t size = vf->size(vf);
|
||||
char* buf = calloc(1, size + 1);
|
||||
assert_int_equal(vf->read(vf, buf, size), size);
|
||||
assert_string_equal(buf, "{\n\t\"a\":\"a\\u0000b\"\n}");
|
||||
free(buf);
|
||||
vf->close(vf);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(deserializeInt) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
static const char* json = "{\"a\":1}";
|
||||
struct VFile* vf = VFileFromConstMemory(json, strlen(json));
|
||||
assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf));
|
||||
TEST_PROGRAM("assert(bucket.a == 1)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(deserializeFloat) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
static const char* json = "{\"a\":0.5}";
|
||||
struct VFile* vf = VFileFromConstMemory(json, strlen(json));
|
||||
assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf));
|
||||
TEST_PROGRAM("assert(bucket.a == 0.5)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(deserializeBool) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
static const char* json = "{\"a\":true}";
|
||||
struct VFile* vf = VFileFromConstMemory(json, strlen(json));
|
||||
assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf));
|
||||
TEST_PROGRAM("assert(bucket.a == true)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(deserializeNil) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
static const char* json = "{\"a\":null}";
|
||||
struct VFile* vf = VFileFromConstMemory(json, strlen(json));
|
||||
assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf));
|
||||
TEST_PROGRAM("assert(bucket.a == nil)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(deserializeString) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
static const char* json = "{\"a\":\"hello\"}";
|
||||
struct VFile* vf = VFileFromConstMemory(json, strlen(json));
|
||||
assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf));
|
||||
TEST_PROGRAM("assert(bucket.a == 'hello')");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(deserializeList) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
static const char* json = "{\"a\":[1,2]}";
|
||||
struct VFile* vf = VFileFromConstMemory(json, strlen(json));
|
||||
assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf));
|
||||
TEST_PROGRAM("assert(#bucket.a == 2)");
|
||||
TEST_PROGRAM("assert(bucket.a[1] == 1)");
|
||||
TEST_PROGRAM("assert(bucket.a[2] == 2)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(deserializeTable) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
static const char* json = "{\"a\":{\"b\":1}}";
|
||||
struct VFile* vf = VFileFromConstMemory(json, strlen(json));
|
||||
assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf));
|
||||
TEST_PROGRAM("assert(bucket.a.b == 1)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(deserializeNullByteString) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
static const char* json = "{\"a\":\"a\\u0000b\"}";
|
||||
struct VFile* vf = VFileFromConstMemory(json, strlen(json));
|
||||
assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf));
|
||||
TEST_PROGRAM("assert(bucket.a == 'a\\x00b')");
|
||||
TEST_PROGRAM("assert(bucket.a ~= 'a\\x00c')");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(deserializeError) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
static const char* json = "{a:1}";
|
||||
struct VFile* vf = VFileFromConstMemory(json, strlen(json));
|
||||
assert_false(mScriptStorageLoadBucketVF(&context, "xtest", vf));
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(structuredRoundTrip) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
TEST_PROGRAM(
|
||||
"bucket.a = {\n"
|
||||
" ['a'] = 1,\n"
|
||||
" ['b'] = {1},\n"
|
||||
" ['c'] = {\n"
|
||||
" ['d'] = 1\n"
|
||||
" }\n"
|
||||
"}"
|
||||
);
|
||||
|
||||
struct VFile* vf = VFileOpen("test.json", O_CREAT | O_TRUNC | O_WRONLY);
|
||||
assert_true(mScriptStorageSaveBucketVF(&context, "xtest", vf));
|
||||
|
||||
TEST_PROGRAM("bucket.a = nil")
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
vf = VFileOpen("test.json", O_RDONLY);
|
||||
assert_non_null(vf);
|
||||
assert_true(mScriptStorageLoadBucketVF(&context, "xtest", vf));
|
||||
|
||||
TEST_PROGRAM("assert(bucket.a)");
|
||||
TEST_PROGRAM("assert(bucket.a.a == 1)");
|
||||
TEST_PROGRAM("assert(#bucket.a.b == 1)");
|
||||
TEST_PROGRAM("assert(bucket.a.b[1] == 1)");
|
||||
TEST_PROGRAM("assert(#bucket.a.c == 1)");
|
||||
TEST_PROGRAM("assert(bucket.a.c.d == 1)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(autoflush) {
|
||||
SETUP_LUA;
|
||||
|
||||
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
|
||||
TEST_PROGRAM("assert(bucket)");
|
||||
TEST_PROGRAM("assert(not bucket.a)");
|
||||
|
||||
TEST_PROGRAM("bucket:enableAutoFlush(true)")
|
||||
TEST_PROGRAM("bucket.a = 1");
|
||||
TEST_PROGRAM("storage:flushAll()");
|
||||
TEST_PROGRAM("assert(bucket:reload())")
|
||||
TEST_PROGRAM("assert(bucket.a == 1)");
|
||||
|
||||
TEST_PROGRAM("bucket:enableAutoFlush(false)")
|
||||
TEST_PROGRAM("bucket.a = 2");
|
||||
TEST_PROGRAM("storage:flushAll()");
|
||||
TEST_PROGRAM("assert(bucket:reload())")
|
||||
TEST_PROGRAM("assert(bucket.a == 1)");
|
||||
|
||||
TEST_PROGRAM("bucket:enableAutoFlush(false)")
|
||||
TEST_PROGRAM("bucket.a = 3");
|
||||
TEST_PROGRAM("storage:flushAll()");
|
||||
TEST_PROGRAM("bucket:enableAutoFlush(true)")
|
||||
TEST_PROGRAM("storage:flushAll()");
|
||||
TEST_PROGRAM("assert(bucket:reload())")
|
||||
TEST_PROGRAM("assert(bucket.a == 3)");
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage,
|
||||
cmocka_unit_test(basicInt),
|
||||
cmocka_unit_test(basicFloat),
|
||||
cmocka_unit_test(basicBool),
|
||||
cmocka_unit_test(basicNil),
|
||||
cmocka_unit_test(basicString),
|
||||
cmocka_unit_test(basicList),
|
||||
cmocka_unit_test(basicTable),
|
||||
cmocka_unit_test(nullByteString),
|
||||
cmocka_unit_test(invalidObject),
|
||||
cmocka_unit_test(structured),
|
||||
cmocka_unit_test(serializeInt),
|
||||
cmocka_unit_test(serializeFloat),
|
||||
cmocka_unit_test(serializeBool),
|
||||
cmocka_unit_test(serializeNil),
|
||||
cmocka_unit_test(serializeString),
|
||||
cmocka_unit_test(serializeList),
|
||||
cmocka_unit_test(serializeTable),
|
||||
cmocka_unit_test(serializeNullByteString),
|
||||
cmocka_unit_test(deserializeInt),
|
||||
cmocka_unit_test(deserializeFloat),
|
||||
cmocka_unit_test(deserializeBool),
|
||||
cmocka_unit_test(deserializeNil),
|
||||
cmocka_unit_test(deserializeString),
|
||||
cmocka_unit_test(deserializeList),
|
||||
cmocka_unit_test(deserializeTable),
|
||||
cmocka_unit_test(deserializeNullByteString),
|
||||
cmocka_unit_test(deserializeError),
|
||||
cmocka_unit_test(structuredRoundTrip),
|
||||
cmocka_unit_test(autoflush),
|
||||
)
|
|
@ -27,6 +27,10 @@ static bool _stringCast(const struct mScriptValue*, const struct mScriptType*, s
|
|||
static bool _castScalar(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*);
|
||||
static uint32_t _hashScalar(const struct mScriptValue*);
|
||||
|
||||
static bool _wstrCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*);
|
||||
static bool _wlistCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*);
|
||||
static bool _wtableCast(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*);
|
||||
|
||||
static uint32_t _valHash(const void* val, size_t len, uint32_t seed);
|
||||
static bool _valEqual(const void* a, const void* b);
|
||||
static void* _valRef(void*);
|
||||
|
@ -232,6 +236,7 @@ const struct mScriptType mSTStringWrapper = {
|
|||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.hash = NULL,
|
||||
.cast = _wstrCast,
|
||||
};
|
||||
|
||||
const struct mScriptType mSTListWrapper = {
|
||||
|
@ -241,6 +246,17 @@ const struct mScriptType mSTListWrapper = {
|
|||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.hash = NULL,
|
||||
.cast = _wlistCast,
|
||||
};
|
||||
|
||||
const struct mScriptType mSTTableWrapper = {
|
||||
.base = mSCRIPT_TYPE_WRAPPER,
|
||||
.size = sizeof(struct mScriptValue),
|
||||
.name = "wrapper table",
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.hash = NULL,
|
||||
.cast = _wtableCast,
|
||||
};
|
||||
|
||||
const struct mScriptType mSTWeakref = {
|
||||
|
@ -366,6 +382,45 @@ uint32_t _hashScalar(const struct mScriptValue* val) {
|
|||
return x;
|
||||
}
|
||||
|
||||
bool _wstrCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) {
|
||||
if (input->type->base != mSCRIPT_TYPE_WRAPPER) {
|
||||
return false;
|
||||
}
|
||||
const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input);
|
||||
if (unwrapped->type != mSCRIPT_TYPE_MS_STR) {
|
||||
return false;
|
||||
}
|
||||
memcpy(output, input, sizeof(*output));
|
||||
output->type = type;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _wlistCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) {
|
||||
if (input->type->base != mSCRIPT_TYPE_WRAPPER) {
|
||||
return false;
|
||||
}
|
||||
const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input);
|
||||
if (unwrapped->type != mSCRIPT_TYPE_MS_LIST) {
|
||||
return false;
|
||||
}
|
||||
memcpy(output, input, sizeof(*output));
|
||||
output->type = type;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _wtableCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) {
|
||||
if (input->type->base != mSCRIPT_TYPE_WRAPPER) {
|
||||
return false;
|
||||
}
|
||||
const struct mScriptValue* unwrapped = mScriptValueUnwrapConst(input);
|
||||
if (unwrapped->type != mSCRIPT_TYPE_MS_TABLE) {
|
||||
return false;
|
||||
}
|
||||
memcpy(output, input, sizeof(*output));
|
||||
output->type = type;
|
||||
return true;
|
||||
}
|
||||
|
||||
#define AS(NAME, TYPE) \
|
||||
bool _as ## NAME(const struct mScriptValue* input, mSCRIPT_TYPE_C_ ## TYPE * T) { \
|
||||
switch (input->type->base) { \
|
||||
|
@ -837,7 +892,6 @@ void mScriptValueWrap(struct mScriptValue* value, struct mScriptValue* out) {
|
|||
|
||||
out->type = mSCRIPT_TYPE_MS_WRAPPER;
|
||||
out->value.opaque = value;
|
||||
mScriptValueRef(value);
|
||||
}
|
||||
|
||||
struct mScriptValue* mScriptValueUnwrap(struct mScriptValue* value) {
|
||||
|
@ -944,14 +998,11 @@ bool mScriptTableRemove(struct mScriptValue* table, struct mScriptValue* key) {
|
|||
}
|
||||
|
||||
struct mScriptValue* mScriptTableLookup(struct mScriptValue* table, struct mScriptValue* key) {
|
||||
if (table->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
table = mScriptValueUnwrap(table);
|
||||
}
|
||||
if (table->type != mSCRIPT_TYPE_MS_TABLE) {
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
if (!key->type->hash) {
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
return HashTableLookupCustom(table->value.table, key);
|
||||
}
|
||||
|
@ -1097,12 +1148,16 @@ static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScript
|
|||
}
|
||||
break;
|
||||
case mSCRIPT_CLASS_INIT_SET:
|
||||
cls->set = calloc(1, sizeof(*member));
|
||||
memcpy(cls->set, &detail->info.member, sizeof(*member));
|
||||
member = calloc(1, sizeof(*member));
|
||||
memcpy(member, &detail->info.member, sizeof(*member));
|
||||
if (docstring) {
|
||||
cls->set->docstring = docstring;
|
||||
member->docstring = docstring;
|
||||
docstring = NULL;
|
||||
}
|
||||
if (detail->info.member.type->details.function.parameters.count != 3) {
|
||||
abort();
|
||||
}
|
||||
HashTableInsert(&cls->setters, detail->info.member.type->details.function.parameters.entries[2]->name, member);
|
||||
break;
|
||||
case mSCRIPT_CLASS_INIT_INTERNAL:
|
||||
cls->internal = true;
|
||||
|
@ -1117,11 +1172,11 @@ void mScriptClassInit(struct mScriptTypeClass* cls) {
|
|||
}
|
||||
HashTableInit(&cls->instanceMembers, 0, free);
|
||||
HashTableInit(&cls->castToMembers, 0, NULL);
|
||||
HashTableInit(&cls->setters, 0, free);
|
||||
|
||||
cls->alloc = NULL;
|
||||
cls->free = NULL;
|
||||
cls->get = NULL;
|
||||
cls->set = NULL;
|
||||
_mScriptClassInit(cls, cls->details, false);
|
||||
|
||||
cls->init = true;
|
||||
|
@ -1133,6 +1188,7 @@ void mScriptClassDeinit(struct mScriptTypeClass* cls) {
|
|||
}
|
||||
HashTableDeinit(&cls->instanceMembers);
|
||||
HashTableDeinit(&cls->castToMembers);
|
||||
HashTableDeinit(&cls->setters);
|
||||
cls->init = false;
|
||||
}
|
||||
|
||||
|
@ -1265,7 +1321,7 @@ bool mScriptObjectGet(struct mScriptValue* obj, const char* member, struct mScri
|
|||
this->type = obj->type;
|
||||
this->refs = mSCRIPT_VALUE_UNREF;
|
||||
this->flags = 0;
|
||||
this->value.opaque = obj;
|
||||
this->value.opaque = obj->value.opaque;
|
||||
mSCRIPT_PUSH(&frame.arguments, CHARP, member);
|
||||
if (!mScriptInvoke(&getMember, &frame) || mScriptListSize(&frame.returnValues) != 1) {
|
||||
mScriptFrameDeinit(&frame);
|
||||
|
@ -1300,6 +1356,91 @@ bool mScriptObjectGetConst(const struct mScriptValue* obj, const char* member, s
|
|||
return _accessRawMember(m, obj->value.opaque, true, val);
|
||||
}
|
||||
|
||||
static struct mScriptClassMember* _findSetter(const struct mScriptTypeClass* cls, const struct mScriptType* type) {
|
||||
struct mScriptClassMember* m = HashTableLookup(&cls->setters, type->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
|
||||
switch (type->base) {
|
||||
case mSCRIPT_TYPE_SINT:
|
||||
if (type->size < 2) {
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S16->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
if (type->size < 4) {
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S32->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
if (type->size < 8) {
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_S64->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case mSCRIPT_TYPE_UINT:
|
||||
if (type->size < 2) {
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U16->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
if (type->size < 4) {
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U32->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
if (type->size < 8) {
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_U64->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case mSCRIPT_TYPE_FLOAT:
|
||||
if (type->size < 8) {
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_F64->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case mSCRIPT_TYPE_STRING:
|
||||
if (type == mSCRIPT_TYPE_MS_STR) {
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_CHARP->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WSTR->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case mSCRIPT_TYPE_LIST:
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WLIST->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
break;
|
||||
case mSCRIPT_TYPE_TABLE:
|
||||
m = HashTableLookup(&cls->setters, mSCRIPT_TYPE_MS_WTABLE->name);
|
||||
if (m) {
|
||||
return m;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScriptValue* val) {
|
||||
if (obj->type->base != mSCRIPT_TYPE_OBJECT || obj->type->isConst) {
|
||||
return false;
|
||||
|
@ -1314,7 +1455,29 @@ bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScri
|
|||
|
||||
struct mScriptClassMember* m = HashTableLookup(&cls->instanceMembers, member);
|
||||
if (!m) {
|
||||
return false;
|
||||
if (val->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
val = mScriptValueUnwrap(val);
|
||||
}
|
||||
struct mScriptValue setMember;
|
||||
m = _findSetter(cls, val->type);
|
||||
if (!m || !_accessRawMember(m, obj->value.opaque, obj->type->isConst, &setMember)) {
|
||||
return false;
|
||||
}
|
||||
struct mScriptFrame frame;
|
||||
mScriptFrameInit(&frame);
|
||||
struct mScriptValue* this = mScriptListAppend(&frame.arguments);
|
||||
this->type = obj->type;
|
||||
this->refs = mSCRIPT_VALUE_UNREF;
|
||||
this->flags = 0;
|
||||
this->value.opaque = obj->value.opaque;
|
||||
mSCRIPT_PUSH(&frame.arguments, CHARP, member);
|
||||
mScriptValueWrap(val, mScriptListAppend(&frame.arguments));
|
||||
if (!mScriptInvoke(&setMember, &frame) || mScriptListSize(&frame.returnValues) != 0) {
|
||||
mScriptFrameDeinit(&frame);
|
||||
return false;
|
||||
}
|
||||
mScriptFrameDeinit(&frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
void* rawMember = (void *)((uintptr_t) obj->value.opaque + m->offset);
|
||||
|
@ -1478,7 +1641,7 @@ bool mScriptPopPointer(struct mScriptList* list, void** out) {
|
|||
}
|
||||
|
||||
bool mScriptCast(const struct mScriptType* type, const struct mScriptValue* input, struct mScriptValue* output) {
|
||||
if (input->type->base == mSCRIPT_TYPE_WRAPPER) {
|
||||
if (input->type->base == mSCRIPT_TYPE_WRAPPER && type->base != mSCRIPT_TYPE_WRAPPER) {
|
||||
input = mScriptValueUnwrapConst(input);
|
||||
}
|
||||
if (type->cast && type->cast(input, type, output)) {
|
||||
|
|