mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'feature/scripting-storage'
This commit is contained in:
commit
292ae8dcf8
|
@ -55,6 +55,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(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support")
|
||||
|
@ -477,6 +478,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)
|
||||
|
@ -765,11 +767,30 @@ 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)
|
||||
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})
|
||||
|
@ -1289,6 +1310,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}")
|
||||
|
|
|
@ -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
|
|
@ -79,6 +79,10 @@
|
|||
#cmakedefine USE_GDB_STUB
|
||||
#endif
|
||||
|
||||
#ifndef USE_JSON_C
|
||||
#cmakedefine USE_JSON_C
|
||||
#endif
|
||||
|
||||
#ifndef USE_LIBAV
|
||||
#cmakedefine USE_LIBAV
|
||||
#endif
|
||||
|
|
|
@ -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();
|
||||
|
@ -144,6 +148,10 @@ void ScriptingController::runCode(const QString& code) {
|
|||
load(vf, "*prompt");
|
||||
}
|
||||
|
||||
void ScriptingController::flushStorage() {
|
||||
mScriptStorageFlushAll(&m_scriptContext);
|
||||
}
|
||||
|
||||
bool ScriptingController::eventFilter(QObject* obj, QEvent* ev) {
|
||||
event(obj, ev);
|
||||
return false;
|
||||
|
@ -293,6 +301,7 @@ void ScriptingController::detachGamepad() {
|
|||
void ScriptingController::init() {
|
||||
mScriptContextInit(&m_scriptContext);
|
||||
mScriptContextAttachStdlib(&m_scriptContext);
|
||||
mScriptContextAttachStorage(&m_scriptContext);
|
||||
mScriptContextAttachSocket(&m_scriptContext);
|
||||
mScriptContextAttachInput(&m_scriptContext);
|
||||
mScriptContextRegisterEngines(&m_scriptContext);
|
||||
|
@ -308,6 +317,8 @@ void ScriptingController::init() {
|
|||
if (m_engines.count() == 1) {
|
||||
m_activeEngine = *m_engines.begin();
|
||||
}
|
||||
|
||||
m_storageFlush.start();
|
||||
}
|
||||
|
||||
uint32_t ScriptingController::qtToScriptingKey(const QKeyEvent* event) {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba/script/input.h>
|
||||
|
@ -55,6 +56,8 @@ public slots:
|
|||
void reset();
|
||||
void runCode(const QString& code);
|
||||
|
||||
void flushStorage();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject*, QEvent*) override;
|
||||
|
||||
|
@ -84,6 +87,8 @@ private:
|
|||
|
||||
std::shared_ptr<CoreController> m_controller;
|
||||
InputController* m_inputController = nullptr;
|
||||
|
||||
QTimer m_storageFlush;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -4,12 +4,17 @@ set(SOURCE_FILES
|
|||
input.c
|
||||
socket.c
|
||||
stdlib.c
|
||||
storage.c
|
||||
types.c)
|
||||
|
||||
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 +22,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})
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -0,0 +1,500 @@
|
|||
/* 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 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 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_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_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);
|
||||
mkdir(out, 0755);
|
||||
|
||||
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 || json_object_object_add(rootObj, ckey, obj) < 0) {
|
||||
ok = false;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
obj = json_object_new_uint64(value->value.u64);
|
||||
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:
|
||||
obj = json_object_new_array_ext(mScriptListSize(value->value.list));
|
||||
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;
|
||||
}
|
||||
|
||||
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_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);
|
||||
}
|
||||
|
||||
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);
|
||||
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_.]+/ are allowed
|
||||
size_t i;
|
||||
for (i = 0; name[i]; ++i) {
|
||||
if (i >= STORAGE_LEN_MAX) {
|
||||
return NULL;
|
||||
}
|
||||
if (!isalnum(name[i]) && name[i] != '_' && name[i] != '.') {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
struct mScriptStorageBucket* bucket = HashTableLookup(&storage->buckets, name);
|
||||
if (bucket) {
|
||||
return bucket;
|
||||
}
|
||||
|
||||
bucket = calloc(1, sizeof(*bucket));
|
||||
bucket->name = strdup(name);
|
||||
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);
|
||||
}
|
|
@ -0,0 +1,554 @@
|
|||
/* 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, "{\"a\":1}");
|
||||
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, "{\"a\":0.5}");
|
||||
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, "{\"a\":true}");
|
||||
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, "{\"a\":null}");
|
||||
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, "{\"a\":\"hello\"}");
|
||||
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, "{\"a\":[1,2]}");
|
||||
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, "{\"a\":{\"b\":1}}");
|
||||
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, "{\"a\":\"a\\u0000b\"}");
|
||||
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_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),
|
||||
)
|
Loading…
Reference in New Issue