Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2024-06-21 00:23:04 -07:00
commit 8718d034d4
48 changed files with 2429 additions and 160 deletions

View File

@ -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:

View File

@ -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}")

View File

@ -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:

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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 {

BIN
res/sgb-icon-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
res/sgb-icon-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/sgb-icon-24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
res/sgb-icon-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
res/sgb-icon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
res/sgb-icon-48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
res/sgb-icon-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

1
res/sgb-icon.svg Normal file
View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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:

View File

@ -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) {

View File

@ -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);

View File

@ -70,6 +70,7 @@ struct mGLES2Shader {
GLuint texLocation;
GLuint texSizeLocation;
GLuint positionLocation;
GLuint outputSizeLocation;
struct mGLES2Uniform* uniforms;
size_t nUniforms;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;
};

View File

@ -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();
}
}

View File

@ -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

View File

@ -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('.'));

View File

@ -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;
};
}

View File

@ -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) {

View File

@ -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;
};
}

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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})

View File

@ -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) {

View File

@ -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);

View File

@ -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);
}

View File

@ -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 },

View File

@ -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;

551
src/script/storage.c Normal file
View File

@ -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);
}

View File

@ -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),
)

View File

@ -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),
)

View File

@ -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),
)

585
src/script/test/storage.c Normal file
View File

@ -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),
)

View File

@ -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)) {