commit 2a9b6ac9f903ce8ffeb7e3c6afa4f5c1554bfeee Author: Green Sky Date: Tue May 23 02:06:58 2023 +0200 inital commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fc74c08 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(solanaceae) + +add_library(solanaceae_plugin + ./solanaceae/plugin/solana_plugin_v1.h + ./solanaceae/plugin/plugin.hpp + ./solanaceae/plugin/plugin.cpp + ./solanaceae/plugin/plugin_manager.hpp + ./solanaceae/plugin/plugin_manager.cpp +) + +target_include_directories(solanaceae_plugin PUBLIC .) +target_compile_features(solanaceae_plugin PUBLIC cxx_std_17) +#target_link_libraries(solanaceae_plugin PUBLIC + #solanaceae_core +#) + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2780797 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +The Code is under the following License, if not stated otherwise: + +MIT License + +Copyright (c) 2023 Erik Scholz + +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. + diff --git a/solanaceae/plugin/plugin.cpp b/solanaceae/plugin/plugin.cpp new file mode 100644 index 0000000..408c54a --- /dev/null +++ b/solanaceae/plugin/plugin.cpp @@ -0,0 +1,124 @@ +#include "./plugin.hpp" + +// https://youtu.be/RsHGUL5E1_s + +#define SOLANA_PLUGIN_HOST +#include "./solana_plugin_v1.h" + +#if (defined(_WIN32) || defined(_WIN64)) + #define WIN32_LEAN_AND_MEAN + #include + #undef WIN32_LEAN_AND_MEAN +#else + #include +#endif + +#if defined(_WIN32) || defined(_WIN64) + #define WINDOWS +#elif defined(__APPLE__) + #define APPLE +#else + #define LINUX +#endif + +#include +#include + +void* Plugin::loadSymbol(const char* s_name) { +#if defined(_WIN32) || defined(_WIN64) + return GetProcAddress(_dl, s_name); +#else + return dlsym(_dl, s_name); +#endif +} + +// loads lib and gets name (and version) +Plugin::Plugin(const char* path) { +#if defined(_WIN32) || defined(_WIN64) + _dl = (void*)LoadLibraryA(path); +#else + _dl = (void*)dlopen(path, RTLD_NOW /*| RTLD_LOCAL*/); +#endif + + if (!_dl) { + std::cerr << "PLG opening '" << path << "' failed\n"; + return; + } + + _fn_name = loadSymbol("solana_plugin_get_name"); + _fn_version = loadSymbol("solana_plugin_get_version"); + _fn_start = loadSymbol("solana_plugin_start"); + _fn_stop = loadSymbol("solana_plugin_stop"); + _fn_tick = loadSymbol("solana_plugin_tick"); + + if (!_fn_name || !_fn_version || !_fn_start || !_fn_stop || !_fn_tick) { + std::cerr << "PLG '" << path << "' misses functions\n"; + return; + } + + name = reinterpret_cast(_fn_name)(); + + if (name.empty()) { + std::cerr << "PLG '" << path << "' misses name\n"; + return; + } + + version = reinterpret_cast(_fn_version)(); + + if (version != SOLANA_PLUGIN_VERSION) { + std::cerr << "PLG '" << path << "' version mismatch IS:" << version << " SHOULD:" << SOLANA_PLUGIN_VERSION << "\n"; + return; + } + + valid_plugin = true; +} + +Plugin::Plugin(Plugin&& other) { + valid_plugin = other.valid_plugin; + name = other.name; + + _dl = other._dl; + other._dl = nullptr; + + _fn_name = other._fn_name; + other._fn_name = nullptr; + + _fn_start = other._fn_start; + other._fn_start = nullptr; + + _fn_stop = other._fn_stop; + other._fn_stop = nullptr; + + _fn_tick = other._fn_tick; + other._fn_tick = nullptr; +} + +// unloads the plugin +Plugin::~Plugin(void) { + if (_dl != nullptr) { +#if defined(_WIN32) || defined(_WIN64) + FreeLibrary(_dl); +#else + dlclose(_dl); +#endif + } +} + +// runs the start function +uint32_t Plugin::start(SolanaAPI* solana_api) const { + assert(valid_plugin); + return reinterpret_cast(_fn_start)(solana_api); +} + +// stop function +void Plugin::stop(void) const { + assert(valid_plugin); + reinterpret_cast(_fn_stop)(); +} + +// tick function +void Plugin::tick(float delta) const { + assert(valid_plugin); + reinterpret_cast(_fn_tick)(delta); +} + diff --git a/solanaceae/plugin/plugin.hpp b/solanaceae/plugin/plugin.hpp new file mode 100644 index 0000000..5b94c14 --- /dev/null +++ b/solanaceae/plugin/plugin.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include + +extern "C" { +struct SolanaAPI; +} + +struct Plugin { + bool valid_plugin = false; + + std::string name; + uint32_t version; + + // void* is platform independent enough, maybe use uint64_t + void* _dl = nullptr; + void* _fn_name = nullptr; // TODO: make variable instead of function? + void* _fn_version = nullptr; // TODO: make variable instead of function? + void* _fn_start = nullptr; + void* _fn_stop = nullptr; + void* _fn_tick = nullptr; + + void* loadSymbol(const char* name); + + // loads lib and gets name (and version) + Plugin(const char* path); + Plugin(Plugin&& other); + Plugin(const Plugin& other) = delete; + + // unloads the plugin + ~Plugin(void); + + + // runs the start function + uint32_t start(SolanaAPI* solana_api) const; + + // stop function + void stop(void) const; + + // tick function + void tick(float delta) const; + + operator bool(void) const { + return valid_plugin; + } +}; + diff --git a/solanaceae/plugin/plugin_manager.cpp b/solanaceae/plugin/plugin_manager.cpp new file mode 100644 index 0000000..772df0b --- /dev/null +++ b/solanaceae/plugin/plugin_manager.cpp @@ -0,0 +1,53 @@ +#include "./plugin_manager.hpp" + +#include + +// def +std::map g_instance_map {}; + +extern "C" { + +void* g_resolveInstance__internal(const char* id) { + if (auto it = g_instance_map.find(id); it != g_instance_map.end()) { + return it->second; + } + return nullptr; +} + +void g_provideInstance__internal(const char* id, const char* plugin_name, void* instance) { + g_instance_map[id] = instance; + std::cout << "PLM '" << plugin_name << "' provided '" << id << "'\n"; +} + + +} // extern C + +PluginManager::~PluginManager(void) { + // destruct in reverse! + for (auto it = _plugins.rbegin(); it != _plugins.rend(); it++) { + it->stop(); + } +} + +bool PluginManager::add(const std::string& plug_path) { + Plugin p{plug_path.c_str()}; + + if (!p) { + return false; + } + + if (p.start(&_sapi) != 0) { + return false; + } + + _plugins.emplace_back(std::move(p)); + + return true; +} + +void PluginManager::tick(float delta) { + for (const auto& p : _plugins) { + p.tick(delta); + } +} + diff --git a/solanaceae/plugin/plugin_manager.hpp b/solanaceae/plugin/plugin_manager.hpp new file mode 100644 index 0000000..539f13e --- /dev/null +++ b/solanaceae/plugin/plugin_manager.hpp @@ -0,0 +1,44 @@ +#pragma once + +#define SOLANA_PLUGIN_HOST +#include + +#include + +#include +#include +#include + +// TODO: save plug name to instance +extern std::map g_instance_map; + +extern "C" { + +void* g_resolveInstance__internal(const char* id); + +void g_provideInstance__internal(const char* id, const char* plugin_name, void* instance); + +} // extern C + +// templated helper, use or make sure vtable is right +template +void g_provideInstance(const char* id, const char* plugin_name, T* instance) { + g_provideInstance__internal(id, plugin_name, instance); +} + +// only on host! +struct PluginManager { + SolanaAPI _sapi { + &g_resolveInstance__internal, + &g_provideInstance__internal, + }; + + std::vector _plugins; + + ~PluginManager(void); + + bool add(const std::string& plug_path); + + void tick(float delta); +}; + diff --git a/solanaceae/plugin/solana_plugin_v1.h b/solanaceae/plugin/solana_plugin_v1.h new file mode 100644 index 0000000..9274aa2 --- /dev/null +++ b/solanaceae/plugin/solana_plugin_v1.h @@ -0,0 +1,58 @@ +#ifndef SOLANA_PLUGIN__H +#define SOLANA_PLUGIN__H + +#include + +#define SOLANA_PLUGIN_VERSION 1 + +#if defined(_MSC_VER) || defined(__MINGW32__) + #define SOLANA_PLUGIN_EXPORT __declspec(dllexport) +#elif defined(__GNUC__) // also clang + #define SOLANA_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else + #error unsupported platform +#endif + +#if defined(SOLANA_PLUGIN_HOST) + #define SOLANA_PLUGIN_DECL +#else + #define SOLANA_PLUGIN_DECL SOLANA_PLUGIN_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// public plugin stuff here + +struct SolanaAPI { + void* (*resolveInstance)(const char* id); + + // resolve_all_instances(const char* id) + void (*provideInstance)(const char* id, const char* plugin_name, void* instance); +}; + +// ---------- info ---------- + +SOLANA_PLUGIN_EXPORT const char* solana_plugin_get_name(void); + +// get the SOLANA_PLUGIN_VERSION the plugin was compiled with +SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_get_version(void); + +// ---------- plugin control ---------- + +// return 0 on success +SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api); + +SOLANA_PLUGIN_EXPORT void solana_plugin_stop(void); + +// called periodically +SOLANA_PLUGIN_EXPORT void solana_plugin_tick(float delta); + + +#ifdef __cplusplus +} +#endif + +#endif // SOLANA_PLUGIN__H +