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 every time you run or check the program.
1. What Modules improve
| Without Modules | With 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
| Term | Exact meaning |
|---|---|
| Cold build | No 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 build | Every 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 build | Some imported source Modules are valid cache hits while changed, new, or invalidated Modules rebuild. This is the usual state during active development. |
| Clean source rebuild | The 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:
- resolve and parse the three source units;
- analyse their declarations and dependency interfaces;
- generate and assemble native code for all three units;
- publish validated cache objects for the two imported Modules; and
- 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
| Change | Expected rebuild |
|---|---|
| No source changes | The entry source compiles; unchanged imported Modules use cached native objects. |
| Function body or Private implementation changes | The 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 changes | The changed Module and affected dependants rebuild because the public interface hash changed. |
| An Include used by a Module changes | The owning Module rebuilds because Includes are part of its implementation closure. |
| A UserLib function that a Module calls loses its declaration, or its parameter or result types change | The Module rebuilds against the current declarations, so the compiler reports any call that no longer matches. An explicit .b3m that calls the function stops the build at its Import, naming the function. Other declaration changes, such as a new DLL or parameter name, rebuild nothing, because every build binds UserLib functions to the DLLs its current declarations name. |
| Debug, target architecture, compiler ABI, runtime ABI, or object-format changes | A 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
- resolved counts the entry and all imported units;
- cached counts valid imported source cache hits;
- misses counts imported source Modules which require native code;
- invalidated counts existing candidates rejected after validation;
- compiled counts units which generated native code; and
- linked counts all native objects merged into the result.
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
- Mark helper Functions, Globals, arrays, Types, Interfaces, and Enums
Privatewhen callers do not need them. - Keep public signatures focused and stable. A body-only change is cheaper for dependants than changing a public declaration.
- Pass values and explicit Types across the boundary instead of exposing internal Globals.
- Group code which usually changes together into the same Module.
- Avoid circular imports; dependencies must form an acyclic graph.
- Give each logical Module identity one source or object location. Matches in different search roots are reported as ambiguous rather than selected by filesystem enumeration order.
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\win64\SpaceGame
blitzcc --mode extended --emit-module ^
-o objects\win64\SpaceGame\Math.b3m modules\SpaceGame\Math.bb
blitzcc --mode extended --module-path objects\win64 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 one platform, Debug or Release, and the
compiler/runtime Module ABI. Build and distribute each variant your users need.
Debug objects embed their source and Include closure for portable debugger
tabs; add -d when both producing and consuming one.
| Platform | Module objects |
|---|---|
| Win32 | Create them with the Win32 compiler. |
| Win64 | Create them with the Win64 compiler. |
| macOS, iOS, and the iOS Simulator | Create them on a Mac, with
Program > Create Module… or bin/blitzcc. One object,
holding the Module's AArch64 assembly text, serves all three. A Module that uses
GUI windows or gadgets works only on macOS: iOS has only the GUI requesters, and
an iOS build that imports such a Module stops with an error that names the
command. |
| Web | None. Web builds (--target web) compile every
Module from source: they cannot import a .b3m, and
--emit-module refuses the web target. A library meant for the web
must include its source. |
Importing an object built for another platform stops the build with an error that names both, for example:
Module 'spacegame.math' was built for Win32; this compiler targets Win64
Within each Module root, a logical import looks for Math.bb
before Math.b3m, so an object beside its source is never used.
Give each platform's objects a root of their own, apart from the source:
SpaceGame\
modules\SpaceGame\Math.bb source, for development and web builds
objects\win32\SpaceGame\Math.b3m for the Win32 compiler
objects\win64\SpaceGame\Math.b3m for the Win64 compiler
objects\apple\SpaceGame\Math.b3m for the Mac compiler
Point each build at one root for the library: the source, or its own platform's objects. Do not configure two roots containing the same logical identity: that is an ambiguous import.
9. The IDE workflow
- Select Program / Language mode / Extended.
- Open Program / Module search paths... and add the root beneath which the dotted namespaces are laid out.
- Use F5, F7, or Create executable normally. The compiler-managed cache is automatic.
- To distribute the active named Module, choose Program / Create module.... The IDE suggests a path derived from its dotted name.
- 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 / Run in browser and Program / Create web build... use
the same search paths. Web builds compile every Module from source, so for them
the paths must lead to each Module's source: a root holding only
.b3m objects stops a web build with an error.
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.
On a Mac, the Program menu has the same commands: Language Mode > Extended, Module Search Paths… (one folder per line), Create Module…, and Rebuild All Source Modules. ⌘R runs the program and ⌘B checks it. Create Module… makes one object for macOS, iOS, and the iOS Simulator, whichever Run On target is chosen.
10. Cache location and controls
The default cache root depends on the platform:
| Platform | Cache root |
|---|---|
| Windows | %LOCALAPPDATA%\Blitz3D+\ModuleCache |
| Mac | ~/Library/Caches/Blitz3D+/ModuleCache |
Beneath it, each platform, Debug/Release, and Module ABI version uses a separate partition:
| Compiler | Release partition | Debug partition |
|---|---|---|
| Win32 | win32\release\abi-4.5 | win32\debug\abi-4.5 |
| Win64 | win64\release\abi-4.5 | win64\debug\abi-4.5 |
| Mac (macOS, iOS, and the iOS Simulator) | apple-arm64/release/abi-4.5 | apple-arm64/debug/abi-4.5 |
Web builds do not use the cache. Every web build compiles each Module from
source, so --module-stats always reports cached=0 for
them.
Advanced command-line controls are:
--module-cache directoryuses an isolated cache root;--no-module-cacheignores and does not publish cached source Modules for that build; and--module-statsprints the build counters described above.
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 the Finder, 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. Web builds cannot use objects, so a library that
should also run in a browser must include its source.
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
- Source Module sample: compile and run a Module directly from source.
- Precompiled Module sample: consume a matching explicit object.
- Module sample instructions: exact source, object, Debug, and target commands.
- Modules language reference: precise declaration, visibility, import, qualification, and compatibility rules.