Last active
August 29, 2015 14:07
-
-
Save MrSmith33/7692328455a19e820a7c to your computer and use it in GitHub Desktop.
A draft of modular application engine.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module moddable; | |
import std.stdio; | |
import std.exception : enforce; | |
import std.datetime; | |
import core.thread : Thread; | |
/// Basic module interface. | |
interface IModule | |
{ | |
// i.e. "Test Module" | |
string name() @property; | |
// valid semver version string. i.e. 0.1.0-rc.1 | |
string semver() @property; | |
// load/create needed resources | |
void load(); | |
// get references to other modules | |
void init(IModuleManager moduleman); | |
} | |
interface IModuleManager | |
{ | |
/// Returns reference to module instance if moduleName was registered. | |
IModule findModule(string moduleName); | |
} | |
M getModule(M)(IModuleManager modman, string moduleName = M.stringof) | |
{ | |
IModule mod = modman.findModule(moduleName); | |
M exactModule = cast(M)mod; | |
enforce(exactModule); | |
return exactModule; | |
} | |
/// Simple implementation of IModuleManager | |
// See GameModule for example usage | |
class ModuleManager : IModuleManager | |
{ | |
IModule[string] modules; | |
void registerModule(IModule moduleInstance) | |
{ | |
assert(moduleInstance); | |
modules[moduleInstance.name] = moduleInstance; | |
} | |
void loadModules() | |
{ | |
foreach(IModule m; modules) | |
{ | |
m.load(); | |
writefln("Loaded module %s %s", m.name, m.semver); | |
} | |
} | |
void initModules() | |
{ | |
foreach(IModule m; modules) | |
{ | |
m.init(this); | |
writefln("Inited module %s %s", m.name, m.semver); | |
} | |
} | |
override IModule findModule(string moduleName) | |
{ | |
return modules[moduleName]; | |
} | |
} | |
/// Updatable module interaface. | |
/// Look at MainLoopModule.registerUpdatableModule(IUpdatableModule) | |
interface IUpdatableModule | |
{ | |
/// Will be called by MainLoopModule every frame | |
void update(double delta); | |
} | |
/// Module where the main loop is located | |
class MainLoopModule : IModule | |
{ | |
override string name() @property { return "MainLoopModule"; } | |
override string semver() @property { return "0.1.0"; } | |
override void load() {} | |
override void init(IModuleManager moduleman) | |
{ | |
evdisp = moduleman.getModule!EventDispatcherModule(); | |
} | |
private: | |
EventDispatcherModule evdisp; | |
IUpdatableModule[] updatableModules; | |
bool _isRunning; | |
public: | |
// There is an option to auto-register all modules found in IModuleManager | |
// if they can be cast to IUpdatableModule | |
void registerUpdatableModule(IUpdatableModule updatableModule) | |
{ | |
updatableModules ~= updatableModule; | |
} | |
/// Main loop execution condition. Will run if true. | |
bool isRunning() @property | |
{ | |
return _isRunning; | |
} | |
/// ditto | |
bool isRunning(bool newIsRunning) @property | |
{ | |
return _isRunning = newIsRunning; | |
} | |
/// Simple main loop | |
void mainLoop() | |
{ | |
isRunning = true; | |
writefln("mainLoop"); | |
TickDuration lastTime = Clock.currAppTick; | |
TickDuration newTime = TickDuration.from!"seconds"(0); | |
while(isRunning) | |
{ | |
newTime = Clock.currAppTick; | |
double delta = (newTime - lastTime).usecs / 1_000_000.0; | |
lastTime = newTime; | |
update(delta); | |
Thread.sleep(10.msecs); | |
} | |
// loop stop | |
// event example | |
evdisp.postEvent(new GameStopEvent); | |
} | |
private void update(double delta) | |
{ | |
foreach(mod; updatableModules) | |
{ | |
mod.update(delta); | |
} | |
} | |
} | |
// Basic event | |
abstract class Event | |
{ | |
bool continuePropagation = true; | |
} | |
class GameStopEvent : Event | |
{ | |
} | |
/// Event | |
class EventDispatcherModule : IModule | |
{ | |
override string name() @property { return "EventDispatcherModule"; } | |
override string semver() @property { return "0.1.0"; } | |
override void load() { } | |
override void init(IModuleManager moduleman) {} | |
void subscribeToEvent(E : Event)(void delegate(E event) handler) | |
{ | |
_eventHandlers[typeid(E)] ~= cast(EventHandler)handler; | |
} | |
void postEvent(E : Event)(E event) | |
{ | |
auto handlers = typeid(E) in _eventHandlers; | |
if (!handlers) return; | |
foreach(handler; *handlers) | |
{ | |
handler(event); | |
if (!event.continuePropagation) return; | |
} | |
} | |
private: | |
alias EventHandler = void delegate(Event event); | |
EventHandler[][TypeInfo] _eventHandlers; | |
} | |
struct PacketMapPacket | |
{ | |
string[] packets; | |
} | |
class NetworkModule : IModule, IUpdatableModule | |
{ | |
override string name() @property { return "NetworkModule"; } | |
override string semver() @property { return "0.1.0"; } | |
override void load() | |
{ | |
// load enet | |
registerPacket!PacketMapPacket(); | |
} | |
override void init(IModuleManager moduleman) | |
{ | |
mainmod = moduleman.getModule!MainLoopModule("MainLoopModule"); | |
mainmod.registerUpdatableModule(this); | |
} | |
override void update(double delta) | |
{ | |
// enet_host_service | |
} | |
// | |
void registerPacket(P)() | |
{ | |
packets ~= typeid(P); | |
} | |
private: | |
MainLoopModule mainmod; | |
TypeInfo[] packets; | |
} | |
// Example module that stops main loop after some duration. | |
// Also subscribes to GameStopEvent | |
class GameStopperModule : IModule, IUpdatableModule | |
{ | |
override string name() @property { return "GameStopperModule"; } | |
// valid semver version string. i.e. 0.1.0-rc.1 | |
override string semver() @property { return "1.0.0"; } | |
// load/create needed resources | |
override void load() { } | |
// get references to other modules | |
override void init(IModuleManager moduleman) | |
{ | |
mainmod = moduleman.getModule!MainLoopModule(); | |
mainmod.registerUpdatableModule(this); | |
evdisp = moduleman.getModule!EventDispatcherModule(); | |
evdisp.subscribeToEvent!GameStopEvent(&onGameStop); | |
} | |
override void update(double delta) | |
{ | |
elapsedTime += delta; | |
if (elapsedTime >= nextPrintTreshold) | |
{ | |
writefln("Update delta %ssecs, elapsed %ssecs", delta, elapsedTime); | |
nextPrintTreshold += 0.05; | |
} | |
if (elapsedTime > 0.2) | |
{ | |
mainmod.isRunning = false; | |
writefln("Stopping"); | |
} | |
} | |
private: | |
MainLoopModule mainmod; | |
EventDispatcherModule evdisp; | |
double elapsedTime = 0; | |
double nextPrintTreshold = 0; | |
void onGameStop(GameStopEvent event) | |
{ | |
writefln("Game stopped"); | |
} | |
} | |
// Main game module | |
class GameModule : IModule | |
{ | |
override string name() @property { return "GameModule"; } | |
// valid semver version string. i.e. 0.1.0-rc.1 | |
override string semver() @property { return "1.0.0"; } | |
// load/create needed resources | |
override void load() { } | |
// get references to other modules | |
override void init(IModuleManager moduleman) { } | |
private: | |
ModuleManager moduleman = new ModuleManager; | |
NetworkModule netmod = new NetworkModule; | |
MainLoopModule mainmod = new MainLoopModule; | |
EventDispatcherModule evdispatcher = new EventDispatcherModule; | |
public: | |
void run() | |
{ | |
// load modules | |
// use module loader here | |
moduleman.registerModule(this); | |
moduleman.registerModule(netmod); | |
moduleman.registerModule(mainmod); | |
moduleman.registerModule(new GameStopperModule); | |
moduleman.registerModule(evdispatcher); | |
moduleman.loadModules(); | |
moduleman.initModules(); | |
mainmod.mainLoop(); | |
} | |
} | |
unittest | |
{ | |
GameModule game = new GameModule; | |
game.run(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment