Help home · Guides · Modules language reference · Module samples

Organising Projects with Modules

Modules give independently written Blitz code stable namespaces and explicit public interfaces. In a larger project they also provide compilation boundaries: unchanged imported Modules can reuse validated native objects from the compiler-managed cache instead of regenerating their implementation on every F5 or F7 build.

Extended mode only: Module, Import, As, and Private are available in Extended mode. Modules improve repeated builds; the first cold build must still compile every source unit.

1. What Modules improve

Without ModulesWith Modules
Included declarations share one global naming space.Each Module owns a stable namespace, so libraries may reuse ordinary names.
Included source is part of the main compilation unit.Each imported Module is compiled and cached as a separate unit.
Implementation helpers are visible to all included code.Private keeps implementation details out of the public interface.
Sharing code requires naming conventions and careful Include ordering.Logical names, aliases, and explicit interfaces make code easier to distribute.

Modules are especially useful for stable subsystems such as mathematics, animation, game-state models, procedural generation, parsers, editor tools, AI, and reusable libraries. A tiny one-file program may not build noticeably faster: cache validation and linking still have a fixed cost.

2. A practical project layout

Keep logical Module names aligned with folders beneath one Module root:

SpaceGame\
    main.bb
    modules\
        SpaceGame\
            Math.bb
            Actors.bb
            Navigation.bb

The declarations inside those files are:

; modules\SpaceGame\Math.bb
Dialect "modern"
Module SpaceGame.Math

Private Global epsilon# = 0.0001

Function NearlyEqual(a#, b#)
    Return Abs(a - b) < epsilon
End Function
; main.bb
Dialect "modern"
Import SpaceGame.Math As Math

If Math.NearlyEqual(1.0, 1.00001) Then Print "Close enough"

Add SpaceGame\modules through Program / Module search paths..., or compile from a command prompt with:

blitzcc --mode extended --module-path modules main.bb

A quoted import such as Import "modules\SpaceGame\Math.bb" As Math is resolved relative to the importing file. Logical imports are usually better for reusable code because callers do not depend on its physical installation location.

3. Cold, warm, partial, and clean builds

TermExact meaning
Cold buildNo imported .bb Module has a valid cache object for its current source/Include closure, dependencies, architecture, Debug/Release kind, and Module ABI. Every source Module generates fresh native code. This commonly occurs on a project's first build or when using a fresh cache partition.
Warm buildEvery unchanged imported .bb Module has a compatible validated cache object. The entry source still compiles, source cache validity is still checked, and every object is still linked.
Partial buildSome imported source Modules are valid cache hits while changed, new, or invalidated Modules rebuild. This is the usual state during active development.
Clean source rebuildThe build deliberately bypasses both reading and publishing compiler-managed source cache entries. Every imported .bb Module compiles, but explicit .b3m imports remain precompiled inputs.

Cold and warm describe the availability of compatible cached source objects, not merely whether the cache directory exists. One project may have a warm build while another project using the same cache root has a cold or partial build.

Work performed by a cold and warm build

For an entry program and two imported source Modules, a cold build does the following:

  1. resolve and parse the three source units;
  2. analyse their declarations and dependency interfaces;
  3. generate and assemble native code for all three units;
  4. publish validated cache objects for the two imported Modules; and
  5. link all three native objects in dependency order.

On a warm build the entry program is compiled again. Imported source is read and parsed so the compiler can discover its imports and hash its source and Include closure. For a valid cache hit, however, the compiler replaces the implementation with its compact public interface and reuses the existing native object. It therefore avoids full implementation analysis, translation, native code generation, and assembly for that Module.

Every object is still linked into the program. Modules reduce repeated compilation work; they do not turn the final Blitz program into a collection of runtime DLLs.

4. Edits and minimal rebuilding

ChangeExpected rebuild
No source changesThe entry source compiles; unchanged imported Modules use cached native objects.
Function body or Private implementation changesThe changed Module rebuilds. Dependants can remain cached when its public and Worker-safety interfaces are unchanged.
Public signature, Type field, Enum, Interface, visibility, or dependency contract changesThe changed Module and affected dependants rebuild because the public interface hash changed.
An Include used by a Module changesThe owning Module rebuilds because Includes are part of its implementation closure.
Debug, target architecture, compiler ABI, runtime ABI, or object-format changesA separate cache partition is used or the incompatible object is rejected.

The entry source is deliberately not treated as a reusable imported Module object. Keeping a small entry program and moving stable, substantial code into Modules gives the cache more useful work to reuse.

5. Measuring cache behaviour

Use --module-stats to inspect deterministic build decisions instead of judging one noisy wall-clock measurement:

blitzcc --mode extended --module-path modules --module-stats main.bb

For an entry program with two imported source Modules, a typical first and second build reports:

MODULE_STATS resolved=3 cached=0 misses=2 invalidated=0 compiled=3 linked=3
MODULE_STATS resolved=3 cached=2 misses=0 invalidated=0 compiled=1 linked=3

Measure a representative edit/rebuild loop as well as a no-change build. Projects benefit most when large, stable Modules remain cached while a smaller entry or gameplay Module changes frequently.

6. Includes and Modules solve different problems

Include is textual. Use it to split one Module implementation across several files which share its namespace, Private declarations, and cache fate. An Include change rebuilds the owning Module.

Import creates a separate compilation unit and dependency interface. Use it when code has a meaningful API, should avoid naming clashes, can be reused elsewhere, or changes on a different schedule.

Do not turn every tiny helper into its own Module. Very fine-grained graphs increase resolution, validation, and link overhead. Prefer cohesive boundaries which hide several implementation details behind a small public surface.

7. Designing cache-friendly interfaces

8. Creating and sharing precompiled Modules

The automatic cache is for local incremental compilation. An explicit .b3m is a distributable, precompiled Module object:

mkdir objects\SpaceGame
blitzcc --mode extended --emit-module ^
    -o objects\SpaceGame\Math.b3m modules\SpaceGame\Math.bb

blitzcc --mode extended --module-path objects main.bb

An explicit object avoids reading the library's implementation source. The compiler validates its metadata, parses its compact public interface, resolves its recorded dependencies, and merges its relocatable native code.

A .b3m is specific to Win32 or Win64, Debug or Release, and the compiler/runtime Module ABI. Build and distribute each required variant. Debug objects embed their source and Include closure for portable debugger tabs; add -d when both producing and consuming one.

Keep source and distributable object roots separate. Do not configure two roots containing the same logical identity: that is an ambiguous import.

9. The IDE workflow

  1. Select Program / Language mode / Extended.
  2. Open Program / Module search paths... and add the root beneath which the dotted namespaces are laid out.
  3. Use F5, F7, or Create executable normally. The compiler-managed cache is automatic.
  4. To distribute the active named Module, choose Program / Create module.... The IDE suggests a path derived from its dotted name.
  5. The current Debug setting controls the object kind. Disable Debug before creating a Release object.

The IDE stores Module search paths in its user preferences and forwards them to every compiler invocation. No project file or manual cache switch is required.

Program / Rebuild all source modules performs a one-off error-check build with --no-module-cache. It neither consumes nor publishes source cache entries, does not delete the existing cache, and does not run the program. The next ordinary build can therefore use the previously validated cache again. Explicit .b3m dependencies are not rebuilt; recreate them from their source when a new object is required.

10. Cache location and controls

The default cache root is:

%LOCALAPPDATA%\Blitz3D+\ModuleCache

Beneath it, Win32/Win64, Debug/Release, and Module ABI versions use separate partitions, for example:

win64\release\abi-3

Advanced command-line controls are:

Normally there is no reason to clear the cache: entries are content-addressed and compatibility-checked. To diagnose storage or permission problems, close active builds and remove only the relevant target/build partition in Explorer, or point one command-line build at a fresh directory with --module-cache.

11. When precompilation helps

Use ordinary source Modules during active development: edits are detected automatically and the cache handles unchanged code. Use explicit .b3m objects when distributing a library without its implementation source, pinning a tested dependency in CI, or removing library implementation work from consumer builds.

Precompiling every frequently edited local Module usually adds packaging work without helping iteration. The transparent source cache is intended for that case.

12. Working examples and reference

Back to Guides