Search code examples
c++parsingdllscriptinggame-engine

C++ game engine scripting can't load variables


So I'm programming scripting for my game engine and everything seems to work but if any variables are involved it crashes in random ways. I get the script by checking the folder in which my scripts are contained. I then get all the dll files in that folder and register them into the ScriptManager. Then in the NativeComponent I say what script I want and then I retrieve said script from the ScriptManager and add it to the component.

NativeScript:

struct DLL_API NativeScript {
        std::unordered_map<unsigned int, std::string> nameToScriptIndex{};
        unsigned int scriptIndex = 0;
        std::array<GameObject*, HGE_MAX_SCRIPTS> scripts{nullptr};

        GameObject* gameObject = nullptr;

        void (*instantiateScript)(NativeScript*, const unsigned int&) = {};

        GameObject* (*instantiateFunction)() = {};
        void(*destroyFunction)(NativeScript*) = {};

        template<typename T>
        void addScript() {
            instantiateFunction = []() {return static_cast<GameObject*>(new T()); };
            destroyFunction = [](NativeScript* nativeScript) {delete nativeScript->gameObject; nativeScript->gameObject = nullptr; };
        }

        //name is the name of the dll file in which the script is in
        void addScript(const std::string& name) {
            nameToScriptIndex[scriptIndex] = name;
            scriptIndex += 1;
            
            instantiateScript = [](NativeScript* nativeScript, const unsigned int& index) {

                if (ScriptManager::getScript(nativeScript->nameToScriptIndex[index]) != nullptr) {
                    GameObject* script = ScriptManager::getScript(nativeScript->nameToScriptIndex[index]);



//I believe the error is this line 
                    nativeScript->scripts[index] = (GameObject*)malloc(sizeof(script));
                    memcpy(nativeScript->scripts[index], script, (sizeof(script)));
                }
                else
                    Debug::systemErr("Couldn't find script: " + nativeScript->nameToScriptIndex[index]);
            };
        }

        void destroyScripts() {

        }

    };

Right now I'm only using the first script in the array for testing purposes

Scene code that runs the scripts:

if (system.getComponentManager()->hasComponent<NativeScript>(entity)) {
                auto& script = system.getComponentManager()->getComponent<NativeScript>(entity);

                if (!script.scripts[0]) {
                    script.instantiateScript(&script, 0);

                    //add the entity reference to the script (to call things like getComponent<>())
                    script.scripts[0]->entity = entity;
                    script.scripts[0]->scene = this;
                    script.scripts[0]->created = true;

                    //startup the script
                    script.scripts[0]->start();
                }

            }

and finally the script itself:

//test script
    class DLL_API TestScript : public Script {
    public:
    public:

        //GuiFrame* frame;

        Vec2f testVariable;

        void start() {
            testVariable = Vec2f();
        }

        void update() {

        }

    };

    __declspec(dllexport) Script* CreateScript()
    {
        return new TestScript();
    }

The errors that I'm normally getting are saying that the variable doesn't exist. I've tried a lot of different export options on the dll but to no avail. If you need any more classes or code just ask.

The error that throws is in the delete operator. I don't have any info or variables, just that it throws an exception in the delete operator.


Solution

  • So I fixed it. I had no idea how to do any of this DLL linking so I found this amazing article here: https://www.gamasutra.com/blogs/ZacHills/20170406/295378/Scripting_with_C.php. I then followed along and then one thing that I did differently was instead of storing the createScript(); function itself I stored a pointer to a script that was created using it. So now that I store the function itself I don't have to manually allocate anything and the function does it for me.

    void ScriptManager::loadScriptFromDLL(const std::string& path, const std::string& _name, const bool& supressError) {
            //credits to https://www.gamasutra.com/blogs/ZacHills/20170406/295378/Scripting_with_C.php bc idk how tf to do this
    
            HINSTANCE dllHandle = LoadLibraryA(path.c_str());
    
            if (!dllHandle) {
                Debug::systemErr("Couldn't load dll file containing scripts at: " + path);
            }
    
            typedef GameObject* (__stdcall* scriptPointer)();
    
            scriptPointer createScript = (scriptPointer)GetProcAddress(dllHandle, "CreateScript");
    
            if (!createScript) {
                Debug::systemErr("Couldn't find the create function of a script in the dll file containing scripts at: " + path + "(Check if there is an export function for the script)");
            }
    
            GameObject* out = createScript();
    
            std::string name = _name.substr(0, _name.length() - 4);
    
            if (scriptPathToName.find(path) == scriptPathToName.end() && scriptNameToScript.find(name) == scriptNameToScript.end()) {
                scriptPathToName[path] = name;
                numberToPath[scriptAmount] = path;
                scriptNameToScript[name] = out;
    
    //HERE
                scriptNameToScriptCreate[name] = createScript;
    
                Debug::systemSuccess("Loaded script: " + name, DebugColor::Blue);
                scriptAmount += 1;
            }
            else {
                if(!supressError)
                Debug::systemErr("Already loaded script file: " + path + ", or already loaded script with name: " + name);
            }
    
        }
    

    In this code shows the line in which I stored the function, the function is stored in an unordered map:

    static std::unordered_map<std::string, GameObject* (__stdcall*)()> scriptNameToScriptCreate;
    

    And to now create a script:

    GameObject* ScriptManager::createScript(const std::string& name) {
        return scriptNameToScriptCreate[name]();
    }
    

    Now in the native script class:

    void addScript(const std::string& name) {
                nameToScriptIndex[scriptIndex] = name;
                scriptIndex += 1;
                
                instantiateScript = [](NativeScript* nativeScript, const unsigned int& index) {
    
                    if (ScriptManager::getScript(nativeScript->nameToScriptIndex[index]) != nullptr)
                        nativeScript->scripts[index] = ScriptManager::createScript(nativeScript->nameToScriptIndex[index]);
                    else
                        Debug::systemErr("Couldn't find script: " + nativeScript->nameToScriptIndex[index]);
                };
            }
    

    instead of:

    void addScript(const std::string& name) {
                nameToScriptIndex[scriptIndex] = name;
                scriptIndex += 1;
                
                instantiateScript = [](NativeScript* nativeScript, const unsigned int& index) {
    
                    if (ScriptManager::getScript(nativeScript->nameToScriptIndex[index]) != nullptr) {
                        GameObject* script = ScriptManager::getScript(nativeScript->nameToScriptIndex[index]);
                        nativeScript->scripts[index] = (GameObject*)malloc(sizeof(script));
                        memcpy(nativeScript->scripts[index], script, (sizeof(script)));
                    }
                    else
                        Debug::systemErr("Couldn't find script: " + nativeScript->nameToScriptIndex[index]);
                };
            }
    

    I doubt this will help anyone but I just wanted to let you guys know how I fixed it. Sorry for the super long answer.