Flamework

repository·master·Indexed 41 days ago

https://github.com/rbxts-flamework/core

An extensible game framework for TypeScript in Roblox, providing high-level abstractions for game development. It features a dependency injection system with @Service and @Controller decorators, lifecycle hooks such as OnInit and OnStart, and a Modding API for runtime modifications and custom decorators. Version 1.3.2.

Tokens
5.4K
Snippets
24
Records
28
Agent score
99%

What's inside Flamework

  1. Implement lifecycle hooks

    master

    Flamework provides several interfaces to hook into the engine's lifecycle. Implementing these interfaces allows your Services or Controllers to perform logic at specific times:

    • OnInit: Called during startup. Use this for setup. You can return a Promise<void> to delay the initialization of other dependencies.
    • OnStart: Called after all onInit calls have completed. This is called asynchronously.
    • OnTick: Equivalent to RunService.Heartbeat. Called every frame after physics.
    • OnPhysics: Equivalent to RunService.Stepped. Called every frame before physics.
    • OnRender: (Client only) Equivalent to RunService.RenderStepped. Called every frame before rendering.
    @Service()
    export class MyLifecycleService implements OnInit, OnStart, OnTick {
        onInit() {
            print("Initializing...");
        }
    
        onStart() {
            print("Started!");
        }
    
        onTick(dt: number) {
            // Logic every frame
        }
    }
  2. Initialize Flamework with ignite()

    master

    Call Flamework.ignite() to start the lifecycle events for all registered classes. This process resolves singletons, handles dependency ordering, and triggers lifecycle hooks like onInit and onStart.

    Important: You must preload all necessary directories using addPaths or addPathsGlob before calling ignite(). Any classes registered after ignition will not have their lifecycle events executed.

    Flamework.addPaths("ServerScriptService");
    Flamework.ignite();
  3. Configure Flamework runtime settings

    master

    Flamework uses a configuration system generated by its transformer. You can control logging, profiling, and dependency warnings via the Metadata.Config interface. Configuration can be applied globally to the game or scoped to specific packages using their package IDs.

    Config Options

    • logLevel: Sets the verbosity of logs. Options are "none" or "verbose".
    • profiling: A boolean determining if profiling is enabled. If not explicitly set, it defaults to RunService.IsStudio().
    • disableDependencyWarnings: A boolean to suppress dependency-related warnings.
    export interface Config {
    	logLevel?: "none" | "verbose";
    	profiling?: boolean;
    	disableDependencyWarnings?: boolean;
    }
  4. Retrieve metadata from an object

    master

    You can retrieve metadata using either 'Own' methods (only looking at the target) or 'Inherited' methods (looking at the target and its parents).

    MethodScopeDescription
    getOwnMetadata<T>(obj, key, property?)OwnReturns the value for the key, or undefined if not found.
    getMetadata<T>(obj, key, property?)InheritedReturns the first value found in the hierarchy.
    getMetadatas<T>(obj, key, property?)InheritedReturns an array of ALL values found in the hierarchy for that key.
    hasMetadata(obj, key, property?)InheritedReturns true if the key exists on the object or its parents.
    hasOwnMetadata(obj, key, property?)OwnReturns true if the key exists specifically on the object/property.
    // Get a single value from the hierarchy
    const val = Reflect.getMetadata<string>(myObj, "version");
    
    // Get all values (e.g., if multiple classes in a hierarchy define it)
    const allVals = Reflect.getMetadatas<string>(myObj, "tags");
    
    // Check existence
    if (Reflect.hasMetadata(myObj, "isInitialized")) {
        // ...
    }
  5. Create metadata-only decorators with createMetaDecorator

    master

    If you only need to attach metadata to a class, method, or property without executing logic at the time of decoration, use Modding.createMetaDecorator. These decorators are more lightweight as they only perform the metadata registration step.

    const myMetaDecorator = Modding.createMetaDecorator("Class");
    
    @myMetaDecorator
    class MyMetadataClass {}
  6. Mark a singleton as Optional

    master

    Apply the @Optional decorator to a @Service or @Controller. An optional singleton will only be instantiated if it is explicitly depended upon by another class or if it is manually included using Flamework.includeOptionalClass(ctor).

    @Service()
    @Optional
    export class MyOptionalService implements OnInit {
        onInit() {}
    }
  7. Inspect decorators with getDecorator and getPropertyDecorators

    master

    You can retrieve metadata attached via decorators using the following methods:

    • getDecorator(object, property?, id?): Retrieves the arguments of a specific decorator on an object or a specific property of that object.
    • getPropertyDecorators(obj, id?): Returns a Map of all properties on an object that are decorated with the specified decorator ID.
    • getDecorators(id?): Retrieves all objects currently associated with a specific decorator ID.
    // Get arguments from a specific property decorator
    const config = Modding.getDecorator(myInstance, "myProperty", "my_decorator_id");
    if (config) {
        print(config.arguments);
    }
    
    // Get all properties on an object using a specific decorator
    const propMap = Modding.getPropertyDecorators(myInstance, "my_decorator_id");
    for (const [propName, data] of propMap) {
        print(`${propName} has config:`, data.arguments);
    }
  8. Resolve singletons with resolveSingleton

    master

    Use Modding.resolveSingleton(ctor) to retrieve an existing instance of a class or instantiate it if it does not yet exist. This is the primary way to access Flamework-managed singletons.

    Note: This method will throw an error if a circular dependency is detected during resolution.

    class MyService {
        // ...
    }
    
    // Retrieves the singleton instance of MyService
    const service = Modding.resolveSingleton(MyService);
  9. Listen for lifecycle and decorator events

    master

    The Modding namespace provides mechanisms to react when objects are registered with specific lifecycle events or decorators.

    Listen for any listener being added

    Use Modding.onListenerAdded(func) to trigger a callback whenever any object is registered in the system.

    Listen for specific decorators or lifecycle events

    Pass an id (either a string or an IdRef) to onListenerAdded to filter for specific events. This is useful for building plugins or tools that react to specific Flamework behaviors.

    Listen for listener removal

    Use Modding.onListenerRemoved(func) to react when an object is removed from the system. You can also provide an id to filter for specific removals.

    // Listen for any object being added
    Modding.onListenerAdded((obj) => {
        print("A new object was registered:", obj);
    });
    
    // Listen specifically for when an object with a specific decorator ID is added
    Modding.onListenerAdded((obj) => {
        print("An object with the target decorator was added:", obj);
    }, "my_decorator_id");
    
    // Listen for removals
    Modding.onListenerRemoved((obj) => {
        print("An object was removed:", obj);
    });
  10. Use the Dependency macro

    master

    The Dependency<T>() macro allows you to retrieve a dependency outside of the standard constructor-based dependency injection lifecycle.

    Warning: This is recommended to be used minimally as it can make code harder to test or stub. It is preferred to pass dependencies into your classes via their constructors.

    // Example of manual dependency resolution
    const myService = Dependency<MyService>();