include/wrenbind17/vm.hpp

include/wrenbind17/vm.hpp #

Namespaces #

Name
wrenbind17

Classes #

Name
class wrenbind17::VM Holds the entire Wren VM from which all of the magic happens.
class wrenbind17::VM::Data

Source code #

#pragma once

#include <wren.hpp>

#include <iostream>
#include <sstream>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <functional>
#include <unordered_map>
#include <vector>
#include <memory>

#include "exception.hpp"
#include "map.hpp"
#include "module.hpp"
#include "variable.hpp"

#ifndef DOXYGEN_SHOULD_SKIP_THIS
namespace std {
    template <> struct hash<std::pair<size_t, size_t>> {
        inline size_t operator()(const std::pair<size_t, size_t>& v) const {
            const std::hash<size_t> hasher;
            return hasher(v.first) ^ hasher(v.second);
        }
    };
} // namespace std
#endif

namespace wrenbind17 {
    typedef std::function<void(const char*)> PrintFn;

    typedef std::function<std::string(const std::vector<std::string>& paths, const std::string& name)> LoadFileFn;

    class VM {
    public:
        inline explicit VM(std::vector<std::string> paths = {"./"}, const size_t initHeap = 1024 * 1024,
                           const size_t minHeap = 1024 * 1024 * 10, const int heapGrowth = 50)
            : data(std::make_unique<Data>()) {

            data->paths = std::move(paths);
            data->printFn = [](const char* text) -> void { std::cout << text; };
            data->loadFileFn = [](const std::vector<std::string>& paths, const std::string& name) -> std::string {
                for (const auto& path : paths) {
                    const auto test = path + "/" + std::string(name) + ".wren";

                    std::ifstream t(test);
                    if (!t)
                        continue;

                    std::string source((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
                    return source;
                }

                throw NotFound();
            };

            wrenInitConfiguration(&data->config);
            data->config.initialHeapSize = initHeap;
            data->config.minHeapSize = minHeap;
            data->config.heapGrowthPercent = heapGrowth;
            data->config.userData = data.get();
#if WREN_VERSION_NUMBER >= 4000 // >= 0.4.0
            data->config.reallocateFn = [](void* memory, size_t newSize, void* userData) -> void* {
                return std::realloc(memory, newSize);
            };
            data->config.loadModuleFn = [](WrenVM* vm, const char* name) -> WrenLoadModuleResult {
                auto res = WrenLoadModuleResult();
                auto& self = *reinterpret_cast<VM::Data*>(wrenGetUserData(vm));

                const auto mod = self.modules.find(name);
                if (mod != self.modules.end()) {
                    auto source = mod->second.str();
                    auto buffer = new char[source.size() + 1];
                    std::memcpy(buffer, &source[0], source.size() + 1);
                    res.source = buffer;
                    res.onComplete = [](WrenVM* vm, const char* name, struct WrenLoadModuleResult result) {
                        delete[] result.source;
                    };
                    return res;
                }

                try {
                    auto source = self.loadFileFn(self.paths, std::string(name));
                    auto buffer = new char[source.size() + 1];
                    std::memcpy(buffer, &source[0], source.size() + 1);
                    res.source = buffer;
                    res.onComplete = [](WrenVM* vm, const char* name, struct WrenLoadModuleResult result) {
                        delete[] result.source;
                    };
                } catch (std::exception& e) {
                    (void)e;
                }
                return res;
            };
#else  // < 0.4.0
            data->config.reallocateFn = std::realloc;
            data->config.loadModuleFn = [](WrenVM* vm, const char* name) -> char* {
                auto& self = *reinterpret_cast<VM::Data*>(wrenGetUserData(vm));

                const auto mod = self.modules.find(name);
                if (mod != self.modules.end()) {
                    auto source = mod->second.str();
                    auto buffer = new char[source.size() + 1];
                    std::memcpy(buffer, &source[0], source.size() + 1);
                    return buffer;
                }

                try {
                    auto source = self.loadFileFn(self.paths, std::string(name));
                    auto buffer = new char[source.size() + 1];
                    std::memcpy(buffer, &source[0], source.size() + 1);
                    return buffer;
                } catch (std::exception& e) {
                    (void)e;
                    return nullptr;
                }
            };
#endif // WREN_VERSION_NUMBER >= 4000
            data->config.bindForeignMethodFn = [](WrenVM* vm, const char* module, const char* className,
                                                  const bool isStatic, const char* signature) -> WrenForeignMethodFn {
                auto& self = *reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
                try {
                    auto& found = self.modules.at(module);
                    auto& klass = found.findKlass(className);
                    return klass.findSignature(signature, isStatic);
                } catch (...) {
                    std::cerr << "Wren foreign method " << signature << " not found in C++" << std::endl;
                    std::abort();
                    return nullptr;
                }
            };
            data->config.bindForeignClassFn = [](WrenVM* vm, const char* module,
                                                 const char* className) -> WrenForeignClassMethods {
                auto& self = *reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
                try {
                    auto& found = self.modules.at(module);
                    auto& klass = found.findKlass(className);
                    return klass.getAllocators();
                } catch (...) {
                    exceptionHandler(vm, std::current_exception());
                    return WrenForeignClassMethods{nullptr, nullptr};
                }
            };
            data->config.writeFn = [](WrenVM* vm, const char* text) {
                auto& self = *reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
                self.printFn(text);
            };
            data->config.errorFn = [](WrenVM* vm, WrenErrorType type, const char* module, const int line,
                                      const char* message) {
                auto& self = *reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
                std::stringstream ss;
                switch (type) {
                    case WREN_ERROR_COMPILE:
                        ss << "Compile error: " << message << " at " << module << ":" << line << "\n";
                        break;
                    case WREN_ERROR_RUNTIME:
                        if (!self.nextError.empty()) {
                            ss << "Runtime error: " << self.nextError << "\n";
                            self.nextError.clear();
                        } else {
                            ss << "Runtime error: " << message << "\n";
                        }
                        break;
                    case WREN_ERROR_STACK_TRACE:
                        ss << "  at: " << module << ":" << line << "\n";
                        break;
                    default:
                        break;
                }
                self.lastError += ss.str();
            };

            data->vm = std::shared_ptr<WrenVM>(wrenNewVM(&data->config), [](WrenVM* ptr) { wrenFreeVM(ptr); });
        }

        inline VM(const VM& other) = delete;

        inline VM(VM&& other) noexcept {
            swap(other);
        }

        inline ~VM() = default;

        inline VM& operator=(const VM& other) = delete;

        inline VM& operator=(VM&& other) noexcept {
            if (this != &other) {
                swap(other);
            }
            return *this;
        }

        inline void swap(VM& other) noexcept {
            std::swap(data, other.data);
        }

        inline void runFromSource(const std::string& name, const std::string& code) {
            const auto result = wrenInterpret(data->vm.get(), name.c_str(), code.c_str());
            if (result != WREN_RESULT_SUCCESS) {
                throw CompileError(getLastError());
            }
            return;
        }

        inline void runFromFile(const std::string& name, const std::string& path) {
            std::ifstream t(path);
            if (!t)
                throw Exception("Compile error: Failed to open source file");
            std::string str((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
            runFromSource(name, str);
        }

        inline void runFromModule(const std::string& name) {
            const auto source = data->loadFileFn(data->paths, name);
            runFromSource(name, source);
        }

        inline Variable find(const std::string& module, const std::string& name) {
            wrenEnsureSlots(data->vm.get(), 1);
            wrenGetVariable(data->vm.get(), module.c_str(), name.c_str(), 0);
            auto* handle = wrenGetSlotHandle(data->vm.get(), 0);
            if (!handle)
                throw NotFound();
            return Variable(std::make_shared<Handle>(data->vm, handle));
        }

        inline ForeignModule& module(const std::string& name) {
            auto it = data->modules.find(name);
            if (it == data->modules.end()) {
                it = data->modules.insert(std::make_pair(name, ForeignModule(name, data->vm.get()))).first;
            }
            return it->second;
        }

        inline void addClassType(const std::string& module, const std::string& name, const size_t hash) {
            data->addClassType(module, name, hash);
        }

        inline void getClassType(std::string& module, std::string& name, const size_t hash) {
            data->getClassType(module, name, hash);
        }

        inline bool isClassRegistered(const size_t hash) const {
            return data->isClassRegistered(hash);
        }

        inline void addClassCast(std::shared_ptr<detail::ForeignPtrConvertor> convertor, const size_t hash,
                                 const size_t other) {
            data->addClassCast(std::move(convertor), hash, other);
        }

        inline detail::ForeignPtrConvertor* getClassCast(const size_t hash, const size_t other) {
            return data->getClassCast(hash, other);
        }

        inline std::string getLastError() {
            return data->getLastError();
        }

        inline void setNextError(std::string str) {
            data->setNextError(std::move(str));
        }

        inline void setPrintFunc(const PrintFn& fn) {
            data->printFn = fn;
        }

        inline void setLoadFileFunc(const LoadFileFn& fn) {
            data->loadFileFn = fn;
        }

        inline void gc() {
            wrenCollectGarbage(data->vm.get());
        }

        class Data {
        public:
            std::shared_ptr<WrenVM> vm;
            WrenConfiguration config;
            std::vector<std::string> paths;
            std::unordered_map<std::string, ForeignModule> modules;
            std::unordered_map<size_t, std::string> classToModule;
            std::unordered_map<size_t, std::string> classToName;
            std::unordered_map<std::pair<size_t, size_t>, std::shared_ptr<detail::ForeignPtrConvertor>> classCasting;
            std::string lastError;
            std::string nextError;
            PrintFn printFn;
            LoadFileFn loadFileFn;

            inline void addClassType(const std::string& module, const std::string& name, const size_t hash) {
                classToModule.insert(std::make_pair(hash, module));
                classToName.insert(std::make_pair(hash, name));
            }

            inline void getClassType(std::string& module, std::string& name, const size_t hash) {
                module = classToModule.at(hash);
                name = classToName.at(hash);
            }

            inline bool isClassRegistered(const size_t hash) const {
                return classToModule.find(hash) != classToModule.end();
            }

            inline void addClassCast(std::shared_ptr<detail::ForeignPtrConvertor> convertor, const size_t hash,
                                     const size_t other) {
                classCasting.insert(std::make_pair(std::make_pair(hash, other), std::move(convertor)));
            }

            inline detail::ForeignPtrConvertor* getClassCast(const size_t hash, const size_t other) {
                return classCasting.at(std::pair(hash, other)).get();
            }

            inline std::string getLastError() {
                std::string str;
                std::swap(str, lastError);
                return str;
            }

            inline void setNextError(std::string str) {
                nextError = std::move(str);
            }
        };

    private:
        std::unique_ptr<Data> data;
    };

#ifndef DOXYGEN_SHOULD_SKIP_THIS
    inline std::shared_ptr<WrenVM> getSharedVm(WrenVM* vm) {
        assert(vm);
        auto self = reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
        assert(self->vm);
        return self->vm;
    }
    inline void addClassType(WrenVM* vm, const std::string& module, const std::string& name, const size_t hash) {
        assert(vm);
        auto self = reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
        self->addClassType(module, name, hash);
    }
    inline void getClassType(WrenVM* vm, std::string& module, std::string& name, const size_t hash) {
        assert(vm);
        auto self = reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
        self->getClassType(module, name, hash);
    }
    inline bool isClassRegistered(WrenVM* vm, const size_t hash) {
        assert(vm);
        auto self = reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
        return self->isClassRegistered(hash);
    }
    inline void addClassCast(WrenVM* vm, std::shared_ptr<detail::ForeignPtrConvertor> convertor, const size_t hash,
                             const size_t other) {
        assert(vm);
        auto self = reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
        self->addClassCast(std::move(convertor), hash, other);
    }
    inline detail::ForeignPtrConvertor* getClassCast(WrenVM* vm, const size_t hash, const size_t other) {
        assert(vm);
        auto self = reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
        return self->getClassCast(hash, other);
    }
    inline std::string getLastError(WrenVM* vm) {
        assert(vm);
        auto self = reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
        return self->getLastError();
    }
    inline void setNextError(WrenVM* vm, std::string str) {
        assert(vm);
        auto self = reinterpret_cast<VM::Data*>(wrenGetUserData(vm));
        self->setNextError(std::move(str));
    }
#endif
} // namespace wrenbind17

Updated on 17 October 2023 at 12:26:25 UTC