Skip to content

PackageHandler

Sky edited this page Dec 24, 2021 · 2 revisions

The codebase for Adonis_2.0 is divided into what are called packages. The lowest level of the system is essentially just a package loader. At the core of this package-based approach is the PackageHandler module. This module is responsible for handling all aspects of package loading, including dependency resolution and initialization.

PackageHandler is used by both the server and the client as part of their loading process.

Package Structure

Packages consist of a folder containing a 'Metadata' module, a 'Client' folder containing an 'Initializer' module, and a 'Server' folder containing an 'Initializer' module.

Metadata

Packages provide information about themselves used by both by PackageHandler during the loading process, as well as general information such as the package's author and description.

Below is an example of the metadata module pulled from an example package. A description of each section has been included. All sections are required.

--// Package Metadata
return {
        --// The name of the package. This should be unique & specific to this package.
	Name = "ExamplePackage";

        --// Package version.
	Version = 1.0;

        --// Package description.
	Description = [[ This package is intended to serve as a starting point for future packages. ]];

        --// Package author
	Author = "Sceleratis";

        --// A reference to the package's Root folder.
	Package = script.Parent;

        --// A list of package names (and optional versions) this package relies on.
        --// If you rely on something from another package, it should be included in this list.
        --// If you do not declare dependencies it cannot be guaranteed that your package will load in the order it needs to.
        --// This means methods/variables you expect to use (like Root.Core) may not be available during loading.
        --// During the loading process, package load order is determined based on dependency resolution.
        --// Packages that rely on other packages will always load after the packages they rely on.
        --// Generally, most/all packages will rely on "Core" and "Utilities" as these packages contain the core functionality of the system.
	Dependencies = {
		"Core",
		"Utilities",
                "ExampleDependency==1.2"
	};
}

Circular Dependencies

A circular dependency occurs when a package somewhere in a given package's dependency chain relies on that given package. To better visualize this, consider the following dependency chain:

  • Package A relies on Package C
  • Package B relies on Package A
  • Package C relies on Package B

See the problem? It's impossible to determine a load order for these packages that satisfies all dependency requirements! Package A relies on Package C, but then later Package C relies on Package B which relies on Package A which relies on Package C which relies on Package B which relies on Package A... and so on. This results in what's called a circular dependency error. Packages whose dependencies result in a circular dependency error will automatically fail to load and will result in a error message.

Client & Server Folders

Packages contain code for both the client and the server. Client and Server code is divided using two folders: one named 'Client' and one named 'Server.' While they're names and roles are different, they are fundamentally identical in usage. Both folders must contain a module named 'Initializer.'

Refer to the following code for the structure of 'Initializer' modules.

--// Initializer functions
return {
        --// The Init function will be called when packages are loaded following dependency resolution.
        --// Init will run first and should generally only be used to declare variables/methods to the system while retrieving a reference to Root and the packages folder.
        --// 'Root' is the main system table containing all sub-tables and methods used throughout the system.
        --// 'Packages' is the packages folder containing all packages, of which this package is one.
	Init = function(Root, Packages)
		--// Do init stuff
		--// print("Hello World! We're in the init!")
	end;

        --// AfterInit functions will load after all Init functions have had a chance to run and do any initialization.
        --// AfterInit functions are called in the same order as Init functions based on dependency resolution.
	AfterInit = function(Root, Packages)
		--// Do stuff after init
		--// print("Hello World! We're in the after-init!")
	end;
}

Note: All 'Server' folders are removed from packages before they are sent to the client to be loaded. However, any other folders/objects will remain. This can be used to share certain modules or assets between the server and client. If something should not be shared with the client, make sure it's contained within the 'Server' folder. On the flip side, if something is intended to be shared with the client, make sure it's not in the 'Server' folder, otherwise it will be removed before it reaches the client.

Methods

GetMetadata(Package: Folder)

Given a package, returns the package's metadata.

GetServerPackages(Packages: {})

Given a table containing packages, returns a list of all packages containing a Server folder.

GetClientPackages(Packages: {})

Given a table containing packages, returns a list of all packages containing a Client folder.

StripPackages(Packages: {}, Remove: string)

Given a list of packages, clones packages and removes objects with a name matching Remove. This is used to remove the 'Server' folder from all client packages before sending packages to the client.

FindDependency(Packages: {}, DependencyName: string, DependencyVersion)

Given a table of packages, a dependency name, and a depedency version (optional) this method will search the package matching DepdencyName and DependencyVersion is within the table.

CheckDependencies(Packages: {}, Package: Folder)

Given a table of packages and a package, checks if the package's dependencies are present within the provided Packages table.

GetOrderedPackageList(Packages: {})

Given a table containing packages, performs dependency resolution and returns an ordered list containing packages in the order they must be loaded in order to satisfy their dependency requirements.

InitPackage(Package: Folder, PackageType: string, ...)

Given a package and package type (Server, Client) this function will perform package initialization, passing the provided tuple of arguments into the package initializer functions.

LoadPackages(Packages: {}, PackageType: string, ...)

Given a table of packages and a package type (Server, Client) this function will perform dependency resolution followed by package initialization, passing the provided params tuple to the package initialization functions.