What framework do I want?

Previous post: The Birth of Frameworks – Zero: Why write Frameworks? It talks about what a frame is.

Frame is a frame, in the game program, apart from the rendering layer engine frame, we refer to the frame is the support of the business logic of the frame, but also a frame, norms and constraints on the developer.

Each framework has its own boundaries and solves domain-specific problems.

Then we will analyze what problems I meet, what needs I have, how to solve the problems and how to realize the needs.

Whether it is a small project or a large project, or a project that is constantly expanding, you want to have a clear code structure that can manage different modules.

The way I tried

Manager of Managers

Many frameworks work this way (including the one I wrote earlier)

This is the teacher Liu Gang “Unity project architecture design and development management” mentioned in a relatively good way ▼

The advantages are: similar to the hierarchical structure, each plays its own role; For example, audio management, scene management, level management, etc., each is a singleton script, used together. The structure is relatively clear. You can reuse

But I personally don’t like it very much

  • Call is not convenient, call chain is too long, cost fingers. Such as uimanager.getInstance ().showui or uimgr.ins.showui
    • Here’s what I want: less typing of m.imgr. showUI and not wanting to import UIMgr
  • Unified management without life cycle, singleton initialization and dependencies are less controllable. Lazy initialization until getInstance is called.
  • Console debug calls are inconvenient, and singletons may have to be individually bound to be exposed globally, as each module would have to do
  • Direct dependencies cannot be dynamically replaced
    • There is a module that I want to switch to different implementations on different platforms, but singletons can’t do that.
  • Most coupled engines are developed and can only be reused in the same engine project

The module dictionary is mounted to the global variable Window

This is the way my previous framework used it

Initialize all modules and inject a module dictionary

The dictionary is then mounted into a global variable.

export class Main {
    constructor(){
        const moduleMap = {

        };
        moduleMap["uiMgr"] = new UIMgr();
        moduleMap["netMgr"] = new NetMgr();

        window["aa"] = moduleMap; }}class UIMgr {}class NetMgr {}Copy the code

Advantages: Easy to call

Cons: a bit dangerous, others know can be called in the console for debugging.

These ways have problems in module management, first do not consider how to facilitate the call, the first implementation of how to manage the core mechanism of the module.

A modular mechanism with a life cycle

Pomelo inspired me

The idea was inspired by Pomelo, a Distributed server framework based on NodeJS developed by netease.

Pomelo supports a pluggable Component extension architecture

Users can load custom components by implementing the component-related interfaces: start, afterStart, stop:

app.load([name], comp, [opts])
Copy the code

Start, afterStart These lifecycle interfaces are similar to cocos and Unity’s component interfaces. The main purpose is to facilitate the handling of dependency references between different modules. For example, A depends on B, but B is not initialized yet.

The respective initializations are handled in start, and the dependency calls are made in afterStart.

These life cycles may not be sufficient for different services. You can expand them based on specific services to meet customized requirements.

For example, login business related:

C modules to rely on data from A and B after logging in, then add two interfaces onLoginInit, onAfterLoginInit.

A and B implement onLoginInit interface for login initialization, and C makes dependency calls on onAfterLoginInit interface.

How do I implement the framework I want?

Module life cycle diagram ▼

Interface design

declare global {
    namespace egf {

        interface IModule {
            /** Module name */key? :string
            /** ** when initialized */onInit? (app: IApp):void;
            /** * When all modules are initialized */onAfterInit? (app: IApp):void;
            /** ** when the module stops */onStop? () :void;
        }
        type BootEndCallback = (isSuccess: boolean) = > void;
        /** * boot program */
        interface IBootLoader {
            /** * boot *@param app* /
            onBoot(app: IApp, bootEnd: BootEndCallback): void;
        }
        /** * main program */
        interface IApp<ModuleMap = any> {
            /** * Program status * 0 not started 1 booting, 2 initializing, 3 running */
            state: number;
            /** * module dictionary */
            moduleMap: ModuleMap;
            /** * boot *@param bootLoaders* /
            bootstrap(bootLoaders: egf.IBootLoader[]): Promise<boolean>;
            /** * initializes */
            init(): void;
            /** * Load the module *@param module* /
            loadModule(module: IModule | any, key? : keyof ModuleMap):void;
            /** * stop */
            stop(): void;
            /** * get the module instance *@param moduleKey* /
            getModule<T extends IModule = any>(moduleKey: keyof ModuleMap): T;
            /** * Check if there is a module *@param moduleKey* /
            hasModule(moduleKey: keyof ModuleMap): boolean; }}}// eslint-disable-next-line @typescript-eslint/semi
export{}Copy the code

Bootloader: CatLib inspired me

There’s one bootloader thing I haven’t talked about, and it’s inspired by CatLib, a Unity framework that I think is great.

What is the mechanism? Take the development test environment and production environment as examples.

There is a debugBootLoader, which handles some module loading and initialization for test purposes, and so on.

When you release the production environment, you can either load the boot through the Debug variable shield or cull the code through the build tool.

Specific implementation can be seen: github.com/AILHC/EasyG…

How to use it?

See Demo project for specific use

Cocoscreator2. X demo github.com/AILHC/egf-c…

The demo cocoscreator3d github.com/AILHC/egf-c…

How to access projects ▼

//FrameworkLoader.ts
import { HelloWorld } from ".. /HelloWorld";
export class FrameworkLoader implements egf.IBootLoader {
    onBoot(app: egf.IApp, bootEnd: egf.BootEndCallback): void {
        const helloWorld = new HelloWorld();
        app.loadModule(helloWorld);
        bootEnd(true); }}//AppMain.ts
import { App } from "@ailhc/egf-core"
import { FrameworkLoader } from "./boot-loaders/FrameworkLoader";
import { setModuleMap, m } from "./ModuleMap";
/** * This is a way of starting and initializing the framework when cocos scripts are loaded * independent of scenario loading and node component mounting */
export class AppMain {
    public static app: App<IModuleMap>;
    public static initFramework() {
        const app = new App<IModuleMap>();
        AppMain.app = app;
        app.bootstrap([new FrameworkLoader()]);
        setModuleMap(app.moduleMap);
        app.init();
        window["m"] = m;// Hang to global, convenient console debugging, production environment can be shielded => security
        m.helloWorld.say();
    }

}
AppMain.initFramework();

Copy the code

It is easy to access the project, just new, bootstrap, init

Injecting modules is also simple

// Add a declaration at the beginning of uimgr. ts
declare global {
    interface IModuleMap {
        uiMgr:UIMgr
    }
}
// At initialization, inject the instance
app.loadModule(UIMgr.getInstance(),"uiMgr");
Copy the code

There is no limit to the type of module to be injected. You can inject business modules such as HeroModule into the module so that business modules can call each other directly. Don’t worry about typescript circular references either.

For example (casually) :

// BattleModule.ts
m.hero.showHero(1);

//HeroModule.ts
m.battle.startTestBattle(1);

Copy the code

Just like RPC calls on the server side.

app.rpc.chat.chatRemote.kick(session, uid, player, function(data){});Copy the code

As for how to make the interface call more convenient, this depends on personal preference, I, with a little magic, so that they use comfortable and a little sense of security. See demo for implementation details

I want to use it in CocosCreator and C3d

Since I used Laya in my work, I used this framework for the project. But I secretly play CocosCreator and CocosCreator3d (why? You know 😉 😉)

I don’t want the hassle of copying source code from project to project, iterating and synchronizing.

It would be nice if you could install it like an NPM package. And the core module is a module, and the other modules are a module.

So I developed a module compilation and release tool, thought very simple before the development, in fact, stepped on the pit for a long time 😂.

What does this module build and publish tool do?

  • Compile the module into IIFE, CommonJS, SystemJS js files
  • Automatically generate a single. D. ts declaration file

This systemJS js file allows CocosCreator3d that does not support NPM packages to be used as NPM packages. Even when Cocos3.0 does support NPM, it will be used in exactly the same way. Use C3d1.2.0 to publish web and wechat games, verify that there is no problem in operation.

import { App } from '@ailhc/egf-core';// Reference it as if it were an NPM package
import { _decorator, Component, Node } from 'cc';
import { m, setModuleMap } from './ModuleMap';
import { FrameworkLoader } from './boot-loaders/FrameworkLoader';
const { ccclass, property } = _decorator;
@ccclass('AppMainComp')
export class AppMainComp extends Component {
    /* class member could be defined like this */
    // dummy = '';

    /* use `property` decorator if your want the member to be serializable */
    // @property
    // serializableDummy = 0;

    onLoad() {
        this._initFramework();
    }
    private _initFramework() {
        const app = new App<IModuleMap>();
        // new TestImport();
        app.bootstrap([new FrameworkLoader()]);
        // app.bootstrap([new FrameworkLoader2()]);
        setModuleMap(app.moduleMap);
        app.init();
        window["m"] = m;// Hang to global, convenient console debugging, production environment can be shielded => security
        m.helloWorld.say();
    }
    start(){}// update (dt) {}
}
Copy the code

How to develop a module

  1. Git Clone github.com/AILHC/EasyG…
  2. Copy packages/package-template project, change the folder name, change the project name in package.json, etc
  3. NPM install initializes the project
  4. Then develop in typescript and export all the code using index.ts files (this can be automated using export-typescript plug-ins that must be older than 0.0.5)
  5. Build and publish using egF build

What are the features of the framework

  • Lightweight modularity mechanism
  • Module life cycle
    • Make module initialization orderly and dependencies manageable
  • Interface oriented programming
    • Convenient implementation details can be replaced, the module can be replaced dynamically
  • Friendly type declarations
    • Click on a type prompt, the string to get the module also has a type prompt, very fragrant.
  • Being based on TypeScript is engine independent
  • Each module library is an NPM package
  • The module library can export a variety of JS formats for Laya, CCC, C3D, and even Unity, Unreal.

What can this framework do?

features

  • Based on a lightweight, dependency free module mechanism, the framework can be customized for different projects, large or small. It can also be scaled up incrementally according to the different phases of the project. It can also be easily accessed at different stages of the project

  • Interface – oriented programming module, the underlying components can be replaced without perception

  • Based on the module development tool, we can develop and publish a single core – independent module for different projects.

    • Easy to refer to other projects
    • Convenient open source
    • Easy to do unit testing
  • Based on the modularization mechanism and supporting development tools, you can build your own library of modules for your company or individual, and reuse them on demand for different projects.

Framework Vision:

Thank you for reading my article and I hope you found it interesting.

Framework development series

  • The Birth of Frameworks – Zero: Why write Frameworks?
  • The birth of the Frame – ONE: I want the frame
  • Not just UI management: General display management
  • A plugin to make Fairygui better
  • A common pool of objects that meets multiple needs
  • Building a game/app artifact: Broadcast
  • A generic socket network module that meets all custom requirements
  • Status management of business development summaries
  • To be continued…

The last

Welcome to pay attention, more content continues to update

Public number search: play game development

QQ group: 1103157878

Blog homepage: ailhc.github. IO /

github: github.com/AILHC