Using the loader module to build Avisynth script libraries
Introduction
The loader module, new to AVSLib version 1.1.0 provides a set of functions and globals that together form a unified loading mechanism of Avisynth "modules" (.avsi scripts).
In particular, the loader module allows for loading modules in a minimal fashion, ie only those modules that are required from the script. This can be accomplished if each individual module loads in a pull-requirements fashion, that is it self-loads any required modules.
That way and with proper partition of library code to modules it is possible to build Avisynth libraries of any complexity while not inducing performance penalties to scripts needing only a small subset of the library's functionality.
For anything that is not been covered by this tutorial, see the loader module functions' documentation as well as the AVSLib's source code for details and working examples.
Module internals
Each module in order to register with the loader module must declare its name and position in the library's hierarchy. This is accomplished by the use of the DefineModule "macro", typically as the first line in the module's script file (it is called a macro because it is designed to be called without been assigned to a variable - it returns last).
What the DefineModule "macro" does is to create a unique global variable and assign true to it. Three string arguments must be supplied to it:
- The name of the library that the module belongs to.
- The name of the package / subpackage that the module belongs to.
- The name of the module itself.
The same arguments must be supplied to LoadModule, in order for the module to be actually loaded (imported) at the main script.
What LoadModule does is to check whether the global variable associated with the module is defined. If it is defined then it is assumed that the module is already loaded and no operation is executed; else a proper Import statement is executed to load the module.
The convention followed by the loader routines to determine the path of the module's script file is briefly presented below:
- The library name determines the root folder where the library's components are installed.
- The package / subpackage name is a relative path (from library's root folder) to the folder where the package's modules are located.
- The module name is the name of the module's script file without the extension (which is assumed to always be ".avsi").
The library must install in Avisynth's plugins folder a file that declares a string constant with name __LIBROOT_{library name} and value the path to the library's root folder.
That file should also declare any configuration constants recognised by the library (see the LoadLibrary documentation for details).
Multiple levels of hierarchy are allowed through the use of the "/" (slash) character. Each part of the path following a "/" is considered a subfolder of the folder determined by the part of the path preceeding the "/".
In addition, each module must explicitly (if a pull-requirements loading fashion is selected as is the case with AVSLib) load any modules that is depended to. This is typically executed at the next lines of the module's script after the call to DefineModule.
In order not to mess with user scripts the load calls should assign their return value to a dummy variable (so that they not override the last special variable).
Because loading a bunch of modules to satisfy dependencies tends to produce an ugly block of consecutive assignments, it is recommended to group them inside triply doublequoted multiline strings which are passed to Eval standard Avisynth function, as in the following example:
DefineModule("avslib", "string", "sprintf") global __load__ = Eval(""" LoadModule("avslib", "base", "core") LoadModule("avslib", "numeric", "core") """) global __load__ = IsPackageLoaded("avslib", "string") ? NOP : Eval(""" LoadModule("avslib", "string", "core") LoadModule("avslib", "string", "search") """) global __load__ = IsPackageLoaded("avslib", "array") ? NOP : Eval(""" LoadModule("avslib", "array", "core") LoadModule("avslib", "array", "slices") LoadModule("avslib", "array", "operators") LoadModule("avslib", "array", "transforms") """)
The IsPackageLoaded and IsModuleLoaded test functions are used to determine if a package or module is already loaded to leave the corresponding block with a NOP if the later is true instead of executing a block of function calls.
Package internals
Packages are declared and loaded with similar to modules mechanisms. Since however they are a collection of modules, there are some specific requirements that must be fulfiled.
Each package is declared in a special .avsi file (__init.avsi) contained at the same folder as the modules (and / or subpackages) that belong to the package.
In order to register with the loader module the package must declare its name and position in the library's hierarchy. This is accomplished by the use of the DefinePackage "macro", typically at the first line in the package's special script file (it is called a macro because it is designed to be called without been assigned to a variable - it returns last).
The only other thing that the package's script file has to do is to load all modules of the package (remember, dependencies fulfilment has been assigned to modules).
In order not to mess with user scripts the load calls should assign their return value to a dummy variable (so that they not override the last special variable).
Because loading a bunch of modules tends to produce an ugly block of consecutive assignments, it is recommended to group them inside triply doublequoted multiline strings which are passed to Eval standard Avisynth function, as in the following example:
DefinePackage("avslib", "numeric") global __load__ = Eval(""" LoadModule("avslib", "numeric", "core") LoadModule("avslib", "numeric", "rounding") LoadModule("avslib", "numeric", "powseries") LoadModule("avslib", "numeric", "functions") LoadModule("avslib", "numeric", "statistics") LoadModule("avslib", "numeric", "curves2d") """)
Library internals
In order for a library to cooperate with the loader, it must follow the general structure anticipated by the loader's routines (see the two sections above). In addition it must provide two script (.avsi) files:
- An .avsi file (__init.avsi) contained at the root folder of the library that loads the various configurations of the library based on the value of the __LIBCONFIG global variable.
- A special .avsi file that must be installed in Avisynth's plugins folder that declares a string constant with name __LIBROOT_{library name} and value the path to the library's root folder.
This is the file that will be called by LoadLibrary; the later will assign to __LIBCONFIG the value passed as the second argument to LoadLibrary.
That file should also declare any configuration constants recognised by the library. Those are the values that are passed as the second argument to LoadLibrary.
An example of a library's __init.avsi is presented below (taken from AVSLib version 1.1.0):
global __load__ = \ __LIBCONFIG == CONFIG_AVSLIB_FULL ? Eval(""" LoadPackage("avslib", "base") LoadPackage("avslib", "numeric") LoadPackage("avslib", "bool") LoadPackage("avslib", "string") LoadPackage("avslib", "array") LoadPackage("avslib", "clip") LoadPackage("avslib", "debug") LoadPackage("avslib", "filters") """) : ( \ __LIBCONFIG == CONFIG_AVSLIB_SCRIPT ? Eval(""" LoadPackage("avslib", "base") LoadPackage("avslib", "numeric") LoadPackage("avslib", "string") LoadPackage("avslib", "debug") """) : ( \ __LIBCONFIG == CONFIG_AVSLIB_ARRAYS ? Eval(""" LoadPackage("avslib", "array") """) : ( \ __LIBCONFIG == CONFIG_AVSLIB_FILTERS ? Eval(""" LoadPackage("avslib", "clip") LoadPackage("avslib", "filters") """) \ : Assert(false, \ "AVSLib: 'config' argument out of accepted range") )))
The last line of the if..elseif..else construct presented above throws an error if the user pass in an invalid configuration argument. Another approach that one might wanted to follow is to silently load the entire library if an out-of-range configuration value is passed in.
An example of a library's declaration file that is copied to Avisynth plugins folder is presented below (taken again from AVSLib version 1.1.0):
# LoadLibrary configuration constants # zero should always be reserved for full loading global CONFIG_AVSLIB_FULL = 0 global CONFIG_AVSLIB_SCRIPT = 1 global CONFIG_AVSLIB_ARRAYS = 2 global CONFIG_AVSLIB_FILTERS = 3 # this will be filled by the setup program upon installation global __LIBROOT_AVSLIB = "path_to_the_library_root_folder"
The names of the global constants are arbitrary; nevertheless a sound convention such as the one presented above (in particular the use of library's name inside the constants' names) is useful to avoid name clashes with other libraries.
Library organisation
The organisation of the library is up to the developer; the only restrictions (a sum up of the previous sections) imposed by the loader are the following:
- There must be at least one package (a good name for small libraries is "base" or "core").
- The name of the folder containing a package must be the same with the visible to user name of the package. The same is true for modules also.
- All visible to the user modules of the package (ie those that LoadModule can be called to load them) must be inside the containing package's folder (utility files that are loaded by the module such as other script files or plugins need not).
- All visible to the user packages of the library (ie those that LoadPackage can be called to load them) must be inside the containing library's folder (utility files - see above - need not).
- Plugins cannot be loaded directly; a thin wrapper module is required (this is intentional, since one of the purposes was to present to the user a single interface for loading anything).
- All script files visible to the user (ie packages, modules and special __init.avsi files) must have the extension .avsi.