Using the loader module to build Avisynth script libraries

Table of contents

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.

[<< to top]

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 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:

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.

[<< to top]

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")
	""")

[<< to top]

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 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.

[<< to top]

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:

[<< to top]