/// ---------------------------------------------------------------------------- /// @file util_i_libraries.nss /// @author Michael A. Sinclair (Squatting Monk) /// @author Ed Burke (tinygiant98) /// @brief This file holds functions for packing scripts into libraries. This /// allows the builder to dramatically reduce the module script count by /// keeping related scripts in the same file. /// @details /// Libraries allow the builder to encapsulate many scripts into one, /// dramatically reducing the script count in the module. In a library, each /// script is a function bound to a unique name and/or number. When the library /// is called, the name is routed to the proper function. /// /// Since each script defined by a library has a unique name to identify it, the /// builder can execute a library script without having to know the file it is /// located in. This makes it easy to create script systems to override behavior /// of another system; you don't have to edit the other system's code, you just /// implement your own function to override it. /// /// ## Anatomy of a Library /// This is an example of a simple library: /// /// ``` nwscript /// #include "util_i_libraries" /// /// void MyFunction() /// { /// // ... /// } /// /// void MyOtherFunction() /// { /// // ... /// } /// /// void OnLibraryLoad() /// { /// RegisterLibraryScript("MyFunction"); /// RegisterLibraryScript("MyOtherFunction"); /// } /// ``` /// /// This script contains custom functions (`MyFunction()` and `MyOtherFunction()`) /// as well as an `OnLibraryLoad()` function. `OnLibraryLoad()` is executed /// whenever the library is loaded by `LoadLibrary()`; it calls /// `RegisterLibraryScript()` to expose the names of the custom functions as /// library scripts. When a library script is called with `RunLibraryScript()`, /// the custom functions are called. /// /// If you want to do something more complicated that can't be handled by a /// single function call, you can pass a unique number to /// `RegisterLibraryScript()` as its second parameter, which will cause /// `RunLibraryScript()` to call a special customizable dispatching function /// called `OnLibraryScript()`. This function takes the name and number of the /// desired function and executes the desired code. For example: /// /// ``` nwscript /// #include "util_i_libraries" /// /// void OnLibraryLoad() /// { /// RegisterLibraryScript("Give50GP", 1); /// RegisterLibraryScript("Give100GP", 2); /// } /// /// void OnLibraryScript(string sScript, int nEntry) /// { /// switch (nEntry) /// { /// case 1: GiveGoldToCreature(OBJECT_SELF, 50); break; /// case 2: GiveGoldToCreature(OBJECT_SELF, 100); break; /// } /// } /// ``` /// /// **Note:** A library does not need to have a `main()` function, because this /// will be automatically generated by the `LoadLibrary()` and /// `RunLibraryScript()` functions. /// /// ## Using a Library /// `util_i_libraries.nss` is needed to load or run library scripts. /// /// To use a library, you must first load it. This will activate the library's /// `OnLibraryLoad()` function and register each desired function. /// /// ``` nwscript /// // Loads a single library /// LoadLibrary("my_l_library"); /// /// // Loads a CSV list of library scripts /// LoadLibraries("pw_l_plugin, dlg_l_example, prr_l_main"); /// /// // Loads all libraries matching a glob pattern /// LoadLibrariesByPattern("*_l_*"); /// /// // Loads all libraries matching a prefix /// LoadLibrariesByPrefix("pw_l_"); /// ``` /// /// If a library implements a script that has already been implemented in /// another library, a warning will be issued and the newer script will take /// precedence. /// /// Calling a library script is done using `RunLibraryScript()`. The name /// supplied should be the name bound to the function in the library's /// `OnLibraryLoad()`. If the name supplied is implemented by a library, the /// library will be JIT compiled and the desired function will be called with /// `ExecuteScriptChunk()`. Otherwise, the name will be assumed to match a /// normal script, which will be executed with `ExecuteScript()`. /// /// ``` nwscript /// // Executes a single library script on OBJECT_SELF /// RunLibraryScript("MyFunction"); /// /// // Executes a CSV list of library scripts, for which oPC will be OBJECT_SELF /// object oPC = GetFirstPC(); /// RunLibraryScripts("MyFunction, MyOtherFunction", oPC); /// ``` /// /// ## Pre-Compiled Libraries /// By default, libraries are run using `ExecuteScriptChunk()`, which JIT /// compiles the script and runs it each time the library script is called. If /// you wish to have your script pre-compiled, you can include the script /// `util_i_library.nss` in your file in place of `util_i_libraries.nss`. This /// script contains a `main()` function that will call either your /// `OnLibraryLoad()` or `OnLibraryScript()` function as appropriate; thus, if /// you use this method, you *must* provide an `OnLibraryScript()` dispatch /// function. /// /// **Note**: `util_i_library.nss` uses the nwnsc `default_function` pragma to /// prevent compilation errors and will not compile with the toolset compiler. /// If this is not desired, you can either comment those lines out or implement /// the `main()` function yourself. /// ---------------------------------------------------------------------------- #include "util_i_debug" #include "util_i_csvlists" #include "util_i_sqlite" #include "util_i_nss" #include "util_i_matching" // ----------------------------------------------------------------------------- // Constants // ----------------------------------------------------------------------------- const string LIB_RETURN = "LIB_RETURN"; ///< The return value of the library const string LIB_LIBRARY = "LIB_LIBRARY"; ///< The library being processed const string LIB_SCRIPT = "LIB_SCRIPT"; ///< The library script name const string LIB_ENTRY = "LIB_ENTRY"; ///< The library script entry number // ----------------------------------------------------------------------------- // Function Prototypes // ----------------------------------------------------------------------------- /// @brief Create a library table in the module's volatile sqlite database. /// @param bReset if TRUE, the table will be dropped if already present. /// @note This is called automatically by the library functions. void CreateLibraryTable(int bReset = FALSE); /// @brief Add a database record associating a script with a library. /// @param sLibrary The script to source from. /// @param sScript The name to associate with the library script. /// @param nEntry A number unique to sLibrary to identify this script. If this /// is 0 and the library has not been pre-compiled, RunLibraryScript() will /// call sScript directly. Otherwise, RunLibraryScript() will run a dispatch /// function that can use this number to execute the correct code. Thus, /// nEntry must be set if sScript does not exactly match the desired /// function name or the function requires parameters. void AddLibraryScript(string sLibrary, string sScript, int nEntry = 0); /// @brief Return the name of the library containing a script from the database. /// @param sScript The name of the library script. string GetScriptLibrary(string sScript); /// @brief Return the entry number associated with a library script. /// @param sScript The name of the library script. int GetScriptEntry(string sScript); /// @brief Return a prepared query with the with the library and entry data /// associated with a library script. /// @param sScript The name of the library script. /// @note This allows users to retrive the same data returned by /// GetScriptLibrary() and GetScriptEntry() with one function. sqlquery GetScriptData(string sScript); /// @brief Return whether a script library has been loaded. /// @param sLibrary The name of the script library file. int GetIsLibraryLoaded(string sLibrary); /// @brief Load a script library by executing its OnLibraryLoad() function. /// @param sLibrary The name of the script library file. /// @param bForce If TRUE, will re-load the library if it was already loaded. void LoadLibrary(string sLibrary, int bForce = FALSE); /// @brief Load a list of script libraries in sequence. /// @param sLibraries A CSV list of libraries to load. /// @param bForce If TRUE, will re-load the library if it was already loaded. void LoadLibraries(string sLibraries, int bForce = FALSE); /// @brief Return a json array of script names with a prefix. /// @param sPrefix The prefix matching the scripts to find. /// @returns A sorted json array of script names, minus the extensions. /// @note The search includes both nss and ncs files, with duplicates removed. json GetScriptsByPrefix(string sPrefix); /// @brief Load all scripts matching the given glob pattern(s). /// @param sPattern A CSV list of glob patterns to match with. Supported syntax: /// - `*`: match zero or more characters /// - `?`: match a single character /// - `[abc]`: match any of a, b, or c /// - `[a-z]`: match any character from a-z /// - other text is matched literally /// @param bForce If TRUE, will-reload the library if it was already loaded. void LoadLibrariesByPattern(string sPattern, int bForce = FALSE); /// @brief Load all scripts with a given prefix as script libraries. /// @param sPrefix A prefix for the desired script libraries. /// @param bForce If TRUE, will re-load the library if it was already loaded. /// @see GetMatchesPattern() for the rules on glob syntax. void LoadLibrariesByPrefix(string sPrefix, int bForce = FALSE); /// @brief Execute a registered library script. /// @param sScript The unique name of the library script. /// @param oSelf The object that should execute the script as OBJECT_SELF. /// @returns The integer value set with LibraryReturn() by sScript. /// @note If sScript is not registered as a library script, it will be executed /// as a regular script instead. int RunLibraryScript(string sScript, object oSelf = OBJECT_SELF); /// @brief Execute a list of registered library scripts in sequence. /// @param sScripts A CSV list of library script names. /// @param oSelf The object that should execute the scripts as OBJECT_SELF. /// @note If any script in sScripts is not registered as a library script, it /// will be executed as a regular script instead. void RunLibraryScripts(string sScripts, object oSelf = OBJECT_SELF); /// @brief Register a script to a library. The script can later be called using /// RunLibraryScript(). /// @param sScript A name for the script. Must be unique in the module. If a /// second script with the same name is registered, it will overwrite the /// first one. This value does not have to match the function or script name. /// @param nEntry A number unique to this library to identify this script. If /// this is 0 and the library has not been pre-compiled, RunLibraryScript() /// will call sScript directly. Otherwise, RunLibraryScript() will run a /// dispatch function that can use this number to execute the correct code. /// Thus, nEntry must be set if sScript does not exactly match the desired /// function name or the function requires parameters. /// @note Must be called within a script library's OnLibraryLoad() function. For /// uses in other places, use AddLibraryScript(). void RegisterLibraryScript(string sScript, int nEntry = 0); /// @brief Set the return value of the currently executing library script. /// @param nValue The value to return to the calling script. void LibraryReturn(int nValue); // ----------------------------------------------------------------------------- // Function Definitions // ----------------------------------------------------------------------------- void CreateLibraryTable(int bReset = FALSE) { SqlCreateTableModule("library_scripts", "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "sLibrary TEXT NOT NULL, " + "sScript TEXT NOT NULL UNIQUE ON CONFLICT REPLACE, " + "nEntry INTEGER NOT NULL);", bReset); } void AddLibraryScript(string sLibrary, string sScript, int nEntry = 0) { CreateLibraryTable(); string sQuery = "INSERT INTO library_scripts (sLibrary, sScript, nEntry) " + "VALUES (@sLibrary, @sScript, @nEntry);"; sqlquery sql = SqlPrepareQueryModule(sQuery); SqlBindString(sql, "@sLibrary", sLibrary); SqlBindString(sql, "@sScript", sScript); SqlBindInt(sql, "@nEntry", nEntry); SqlStep(sql); } string GetScriptFieldData(string sField, string sScript) { CreateLibraryTable(); string sQuery = "SELECT " + sField + " FROM library_scripts " + "WHERE sScript = @sScript;"; sqlquery sql = SqlPrepareQueryModule(sQuery); SqlBindString(sql, "@sScript", sScript); return SqlStep(sql) ? SqlGetString(sql, 0) : ""; } string GetScriptLibrary(string sScript) { return GetScriptFieldData("sLibrary", sScript); } int GetScriptEntry(string sScript) { return StringToInt(GetScriptFieldData("nEntry", sScript)); } sqlquery GetScriptData(string sScript) { CreateLibraryTable(); string sQuery = "SELECT sLibrary, nEntry FROM library_scripts " + "WHERE sScript = @sScript;"; sqlquery sql = SqlPrepareQueryModule(sQuery); SqlBindString(sql, "@sScript", sScript); return sql; } int GetIsLibraryLoaded(string sLibrary) { CreateLibraryTable(); string sQuery = "SELECT COUNT(sLibrary) FROM library_scripts " + "WHERE sLibrary = @sLibrary LIMIT 1;"; sqlquery sql = SqlPrepareQueryModule(sQuery); SqlBindString(sql, "@sLibrary", sLibrary); return SqlStep(sql) ? SqlGetInt(sql, 0) : FALSE; } void LoadLibrary(string sLibrary, int bForce = FALSE) { Debug("Attempting to " + (bForce ? "force " : "") + "load library " + sLibrary); if (bForce || !GetIsLibraryLoaded(sLibrary)) { SetScriptParam(LIB_LIBRARY, sLibrary); if (ResManGetAliasFor(sLibrary, RESTYPE_NCS) == "") { Debug(sLibrary + ".ncs not present; loading library as chunk"); string sChunk = NssInclude(sLibrary) + NssVoidMain(NssFunction("OnLibraryLoad")); string sError = ExecuteScriptChunk(sChunk, GetModule(), FALSE); if (sError != "") CriticalError("Could not load " + sLibrary + ": " + sError); } else ExecuteScript(sLibrary, GetModule()); } else Error("Library " + sLibrary + " already loaded!"); } void LoadLibraries(string sLibraries, int bForce = FALSE) { Debug("Attempting to " + (bForce ? "force " : "") + "load libraries " + sLibraries); int i, nCount = CountList(sLibraries); for (i = 0; i < nCount; i++) LoadLibrary(GetListItem(sLibraries, i), bForce); } // Private function for GetScriptsByPrefix*(). Adds all scripts of nResType // matching a prefix to a json array and returns it. json _GetScriptsByPrefix(json jArray, string sPrefix, int nResType) { int i; string sScript; while ((sScript = ResManFindPrefix(sPrefix, nResType, ++i)) != "") jArray = JsonArrayInsert(jArray, JsonString(sScript)); return jArray; } json GetScriptsByPrefix(string sPrefix) { json jScripts = _GetScriptsByPrefix(JsonArray(), sPrefix, RESTYPE_NCS); jScripts = _GetScriptsByPrefix(jScripts, sPrefix, RESTYPE_NSS); jScripts = JsonArrayTransform(jScripts, JSON_ARRAY_UNIQUE); jScripts = JsonArrayTransform(jScripts, JSON_ARRAY_SORT_ASCENDING); return jScripts; } void LoadLibrariesByPattern(string sPatterns, int bForce = FALSE) { if (sPatterns == "") return; Debug("Finding libraries matching \"" + sPatterns + "\""); json jPatterns = ListToJson(sPatterns); json jLibraries = FilterByPatterns(GetScriptsByPrefix(""), jPatterns, TRUE); LoadLibraries(JsonToList(jLibraries), bForce); } void LoadLibrariesByPrefix(string sPrefix, int bForce = FALSE) { Debug("Finding libraries with prefix \"" + sPrefix + "\""); json jLibraries = GetScriptsByPrefix(sPrefix); LoadLibraries(JsonToList(jLibraries), bForce); } void LoadPrefixLibraries(string sPrefix, int bForce = FALSE) { Debug("LoadPrefixLibraries() is deprecated; use LoadLibrariesByPrefix()"); LoadLibrariesByPrefix(sPrefix, bForce); } int RunLibraryScript(string sScript, object oSelf = OBJECT_SELF) { if (sScript == "") return -1; string sLibrary; int nEntry; sqlquery sqlScriptData = GetScriptData(sScript); if (SqlStep(sqlScriptData)) { sLibrary = SqlGetString(sqlScriptData, 0); nEntry = SqlGetInt(sqlScriptData, 1); } DeleteLocalInt(oSelf, LIB_RETURN); if (sLibrary != "") { Debug("Library script " + sScript + " found in " + sLibrary + (nEntry != 0 ? " at entry " + IntToString(nEntry) : "")); SetScriptParam(LIB_LIBRARY, sLibrary); SetScriptParam(LIB_SCRIPT, sScript); SetScriptParam(LIB_ENTRY, IntToString(nEntry)); if (ResManGetAliasFor(sLibrary, RESTYPE_NCS) == "") { Debug(sLibrary + ".ncs not present; running library script as chunk"); string sChunk = NssInclude(sLibrary) + NssVoidMain(nEntry ? NssFunction("OnLibraryScript", NssQuote(sScript) + ", " + IntToString(nEntry)) : NssFunction(sScript)); string sError = ExecuteScriptChunk(sChunk, oSelf, FALSE); if (sError != "") CriticalError("RunLibraryScript(" + sScript +") failed: " + sError); } else ExecuteScript(sLibrary, oSelf); } else { Debug(sScript + " is not a library script; executing directly"); ExecuteScript(sScript, oSelf); } return GetLocalInt(oSelf, LIB_RETURN); } void RunLibraryScripts(string sScripts, object oSelf = OBJECT_SELF) { int i, nCount = CountList(sScripts); for (i = 0; i < nCount; i++) RunLibraryScript(GetListItem(sScripts, i), oSelf); } void RegisterLibraryScript(string sScript, int nEntry = 0) { string sLibrary = GetScriptParam(LIB_LIBRARY); string sExist = GetScriptLibrary(sScript); if (sLibrary != sExist && sExist != "") Warning(sLibrary + " is overriding " + sExist + "'s implementation of " + sScript); int nOldEntry = GetScriptEntry(sScript); if (nOldEntry) Warning(sLibrary + " already declared " + sScript + " Old Entry: " + IntToString(nOldEntry) + " New Entry: " + IntToString(nEntry)); AddLibraryScript(sLibrary, sScript, nEntry); } void LibraryReturn(int nValue) { SetLocalInt(OBJECT_SELF, LIB_RETURN, nValue); }