475 lines
13 KiB
C
475 lines
13 KiB
C
#include "box_engine.h"
|
|
|
|
#include "lib_engine.h"
|
|
#include "lib_input.h"
|
|
#include "lib_node.h"
|
|
#include "lib_standard.h"
|
|
#include "lib_random.h"
|
|
#include "lib_runner.h"
|
|
#include "repl_tools.h"
|
|
|
|
#include "toy_memory.h"
|
|
#include "toy_lexer.h"
|
|
#include "toy_parser.h"
|
|
#include "toy_compiler.h"
|
|
#include "toy_interpreter.h"
|
|
#include "toy_literal_array.h"
|
|
#include "toy_literal_dictionary.h"
|
|
|
|
#include "toy_console_colors.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
//define the extern engine object
|
|
Box_Engine engine;
|
|
|
|
//errors here should be fatal
|
|
static void fatalError(char* message) {
|
|
fprintf(stderr, TOY_CC_ERROR "%s" TOY_CC_RESET, message);
|
|
exit(-1);
|
|
}
|
|
|
|
//exposed functions
|
|
void Box_initEngine() {
|
|
//clear
|
|
engine.rootNode = NULL;
|
|
engine.nextRootNodeFilename = TOY_TO_NULL_LITERAL;
|
|
engine.running = false;
|
|
engine.window = NULL;
|
|
engine.renderer = NULL;
|
|
|
|
//init SDL
|
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
|
fatalError("Failed to initialize SDL2");
|
|
}
|
|
|
|
//init SDL_image
|
|
int imageFlags = IMG_INIT_PNG | IMG_INIT_JPG;
|
|
if (IMG_Init(imageFlags) != imageFlags) {
|
|
fatalError("Failed to initialize SDL2_image");
|
|
}
|
|
|
|
//init SDL_ttf
|
|
if (TTF_Init() == -1) {
|
|
fatalError("Failed to initialize SDL2_ttf");
|
|
}
|
|
|
|
//init events
|
|
Toy_initLiteralDictionary(&engine.symKeyDownEvents);
|
|
Toy_initLiteralDictionary(&engine.symKeyUpEvents);
|
|
|
|
//init Toy
|
|
Toy_initInterpreter(&engine.interpreter);
|
|
Toy_injectNativeHook(&engine.interpreter, "standard", Toy_hookStandard);
|
|
Toy_injectNativeHook(&engine.interpreter, "random", Toy_hookRandom);
|
|
Toy_injectNativeHook(&engine.interpreter, "runner", Toy_hookRunner);
|
|
Toy_injectNativeHook(&engine.interpreter, "engine", Box_hookEngine);
|
|
Toy_injectNativeHook(&engine.interpreter, "node", Box_hookNode);
|
|
Toy_injectNativeHook(&engine.interpreter, "input", Box_hookInput);
|
|
|
|
//run the init
|
|
size_t size = 0;
|
|
const unsigned char* source = Toy_readFile("./assets/scripts/init.toy", &size);
|
|
const unsigned char* tb = Toy_compileString((const char*)source, &size);
|
|
free((void*)source);
|
|
|
|
//A quirk of the setup is that anything defined in `init.toy` becomes a global object
|
|
Toy_runInterpreter(&engine.interpreter, tb, size);
|
|
}
|
|
|
|
void Box_freeEngine() {
|
|
//clear existing root node
|
|
if (engine.rootNode != NULL) {
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onFree", NULL);
|
|
Box_freeEngineNode(engine.rootNode);
|
|
engine.rootNode = NULL;
|
|
}
|
|
|
|
if (!TOY_IS_NULL(engine.nextRootNodeFilename)) {
|
|
Toy_freeLiteral(engine.nextRootNodeFilename);
|
|
}
|
|
|
|
Toy_freeInterpreter(&engine.interpreter);
|
|
|
|
//free events
|
|
Toy_freeLiteralDictionary(&engine.symKeyDownEvents);
|
|
Toy_freeLiteralDictionary(&engine.symKeyUpEvents);
|
|
|
|
//free SDL
|
|
SDL_DestroyRenderer(engine.renderer);
|
|
SDL_DestroyWindow(engine.window);
|
|
SDL_Quit();
|
|
|
|
engine.renderer = NULL;
|
|
engine.window = NULL;
|
|
}
|
|
|
|
static inline void execLoadRootNode() {
|
|
//if a new root node is NOT needed, skip out
|
|
if (TOY_IS_NULL(engine.nextRootNodeFilename)) {
|
|
return;
|
|
}
|
|
|
|
//free the existing root node
|
|
if (engine.rootNode != NULL) {
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onFree", NULL);
|
|
Box_freeEngineNode(engine.rootNode);
|
|
engine.rootNode = NULL;
|
|
}
|
|
|
|
//compile the new root node
|
|
size_t size = 0;
|
|
const unsigned char* source = Toy_readFile(Toy_toCString(TOY_AS_STRING(engine.nextRootNodeFilename)), &size);
|
|
const unsigned char* tb = Toy_compileString((const char*)source, &size);
|
|
free((void*)source);
|
|
|
|
//allocate the new root node
|
|
engine.rootNode = TOY_ALLOCATE(Box_EngineNode, 1);
|
|
|
|
//BUGFIX: make an inner-interpreter
|
|
Toy_Interpreter inner;
|
|
|
|
//init the inner interpreter manually
|
|
Toy_initLiteralArray(&inner.literalCache);
|
|
inner.scope = Toy_pushScope(engine.interpreter.scope);
|
|
inner.bytecode = tb;
|
|
inner.length = size;
|
|
inner.count = 0;
|
|
inner.codeStart = -1;
|
|
inner.depth = engine.interpreter.depth + 1;
|
|
inner.panic = false;
|
|
Toy_initLiteralArray(&inner.stack);
|
|
inner.hooks = engine.interpreter.hooks;
|
|
Toy_setInterpreterPrint(&inner, engine.interpreter.printOutput);
|
|
Toy_setInterpreterAssert(&inner, engine.interpreter.assertOutput);
|
|
Toy_setInterpreterError(&inner, engine.interpreter.errorOutput);
|
|
|
|
Box_initEngineNode(engine.rootNode, &inner, tb, size);
|
|
|
|
//immediately call onLoad() after running the script - for loading other nodes
|
|
Box_callEngineNode(engine.rootNode, &inner, "onLoad", NULL);
|
|
|
|
//cache the scope for later freeing
|
|
engine.rootNode->scope = inner.scope;
|
|
|
|
//manual cleanup
|
|
Toy_freeLiteralArray(&inner.stack);
|
|
Toy_freeLiteralArray(&inner.literalCache);
|
|
|
|
//cleanup
|
|
Toy_freeLiteral(engine.nextRootNodeFilename);
|
|
engine.nextRootNodeFilename = TOY_TO_NULL_LITERAL;
|
|
|
|
//init the new node-tree
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onInit", NULL);
|
|
}
|
|
|
|
static inline void execEvents() {
|
|
Toy_LiteralArray args; //save some allocation by reusing this
|
|
Toy_initLiteralArray(&args);
|
|
|
|
//poll all events
|
|
SDL_Event event;
|
|
|
|
while (SDL_PollEvent(&event)) {
|
|
switch(event.type) {
|
|
//quit
|
|
case SDL_QUIT: {
|
|
engine.running = false;
|
|
}
|
|
break;
|
|
|
|
//window events are handled internally
|
|
case SDL_WINDOWEVENT: {
|
|
switch(event.window.event) {
|
|
case SDL_WINDOWEVENT_RESIZED:
|
|
//TODO: toy onWindowResized, setLogicalWindowSize, getLogicalWindowSize
|
|
//engine.screenWidth = event.window.data1;
|
|
//engine.screenHeight = event.window.data2;
|
|
//SDL_RenderSetLogicalSize(engine.renderer, engine.screenWidth, engine.screenHeight);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
//input
|
|
case SDL_KEYDOWN: {
|
|
//bugfix: ignore repeat messages
|
|
if (event.key.repeat) {
|
|
break;
|
|
}
|
|
|
|
//determine the given keycode
|
|
Toy_Literal keycodeLiteral = TOY_TO_INTEGER_LITERAL( (int)(event.key.keysym.sym) );
|
|
if (!Toy_existsLiteralDictionary(&engine.symKeyDownEvents, keycodeLiteral)) {
|
|
Toy_freeLiteral(keycodeLiteral);
|
|
break;
|
|
}
|
|
|
|
//get the event name
|
|
Toy_Literal eventLiteral = Toy_getLiteralDictionary(&engine.symKeyDownEvents, keycodeLiteral);
|
|
|
|
//call the function
|
|
Toy_pushLiteralArray(&args, eventLiteral);
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onKeyDown", &args);
|
|
Toy_freeLiteral(Toy_popLiteralArray(&args));
|
|
|
|
//push to the event list
|
|
Toy_freeLiteral(eventLiteral);
|
|
Toy_freeLiteral(keycodeLiteral);
|
|
}
|
|
break;
|
|
|
|
case SDL_KEYUP: {
|
|
//bugfix: ignore repeat messages
|
|
if (event.key.repeat) {
|
|
break;
|
|
}
|
|
|
|
//determine the given keycode
|
|
Toy_Literal keycodeLiteral = TOY_TO_INTEGER_LITERAL( (int)(event.key.keysym.sym) );
|
|
if (!Toy_existsLiteralDictionary(&engine.symKeyUpEvents, keycodeLiteral)) {
|
|
Toy_freeLiteral(keycodeLiteral);
|
|
break;
|
|
}
|
|
|
|
//get the event name
|
|
Toy_Literal eventLiteral = Toy_getLiteralDictionary(&engine.symKeyUpEvents, keycodeLiteral);
|
|
|
|
//call the function
|
|
Toy_pushLiteralArray(&args, eventLiteral);
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onKeyUp", &args);
|
|
Toy_freeLiteral(Toy_popLiteralArray(&args));
|
|
|
|
//push to the event list
|
|
Toy_freeLiteral(eventLiteral);
|
|
Toy_freeLiteral(keycodeLiteral);
|
|
}
|
|
break;
|
|
|
|
//mouse motion
|
|
case SDL_MOUSEMOTION: {
|
|
Toy_Literal mouseX = TOY_TO_INTEGER_LITERAL( (int)(event.motion.x) );
|
|
Toy_Literal mouseY = TOY_TO_INTEGER_LITERAL( (int)(event.motion.y) );
|
|
Toy_Literal mouseXRel = TOY_TO_INTEGER_LITERAL( (int)(event.motion.xrel) );
|
|
Toy_Literal mouseYRel = TOY_TO_INTEGER_LITERAL( (int)(event.motion.yrel) );
|
|
|
|
Toy_pushLiteralArray(&args, mouseX);
|
|
Toy_pushLiteralArray(&args, mouseY);
|
|
Toy_pushLiteralArray(&args, mouseXRel);
|
|
Toy_pushLiteralArray(&args, mouseYRel);
|
|
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onMouseMotion", &args);
|
|
|
|
Toy_freeLiteral(mouseX);
|
|
Toy_freeLiteral(mouseY);
|
|
Toy_freeLiteral(mouseXRel);
|
|
Toy_freeLiteral(mouseYRel);
|
|
|
|
//hack: manual free
|
|
for(int i = 0; i < args.count; i++) {
|
|
Toy_freeLiteral(args.literals[i]);
|
|
}
|
|
args.count = 0;
|
|
}
|
|
break;
|
|
|
|
//mouse button down
|
|
case SDL_MOUSEBUTTONDOWN: {
|
|
Toy_Literal mouseX = TOY_TO_INTEGER_LITERAL( (int)(event.button.x) );
|
|
Toy_Literal mouseY = TOY_TO_INTEGER_LITERAL( (int)(event.button.y) );
|
|
Toy_Literal mouseButton;
|
|
|
|
switch (event.button.button) {
|
|
case SDL_BUTTON_LEFT:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("left"));
|
|
break;
|
|
|
|
case SDL_BUTTON_MIDDLE:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("middle"));
|
|
break;
|
|
|
|
case SDL_BUTTON_RIGHT:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("right"));
|
|
break;
|
|
|
|
case SDL_BUTTON_X1:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("x1"));
|
|
break;
|
|
|
|
case SDL_BUTTON_X2:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("x2"));
|
|
break;
|
|
|
|
default:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("unknown"));
|
|
break;
|
|
}
|
|
|
|
Toy_pushLiteralArray(&args, mouseX);
|
|
Toy_pushLiteralArray(&args, mouseY);
|
|
Toy_pushLiteralArray(&args, mouseButton);
|
|
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onMouseButtonDown", &args);
|
|
|
|
Toy_freeLiteral(mouseX);
|
|
Toy_freeLiteral(mouseY);
|
|
Toy_freeLiteral(mouseButton);
|
|
|
|
//hack: manual free
|
|
for(int i = 0; i < args.count; i++) {
|
|
Toy_freeLiteral(args.literals[i]);
|
|
}
|
|
args.count = 0;
|
|
}
|
|
break;
|
|
|
|
//mouse button up
|
|
case SDL_MOUSEBUTTONUP: {
|
|
Toy_Literal mouseX = TOY_TO_INTEGER_LITERAL( (int)(event.button.x) );
|
|
Toy_Literal mouseY = TOY_TO_INTEGER_LITERAL( (int)(event.button.y) );
|
|
Toy_Literal mouseButton;
|
|
|
|
switch (event.button.button) {
|
|
case SDL_BUTTON_LEFT:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("left"));
|
|
break;
|
|
|
|
case SDL_BUTTON_MIDDLE:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("middle"));
|
|
break;
|
|
|
|
case SDL_BUTTON_RIGHT:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("right"));
|
|
break;
|
|
|
|
case SDL_BUTTON_X1:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("x1"));
|
|
break;
|
|
|
|
case SDL_BUTTON_X2:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("x2"));
|
|
break;
|
|
|
|
default:
|
|
mouseButton = TOY_TO_STRING_LITERAL(Toy_createRefString("unknown"));
|
|
break;
|
|
}
|
|
|
|
Toy_pushLiteralArray(&args, mouseX);
|
|
Toy_pushLiteralArray(&args, mouseY);
|
|
Toy_pushLiteralArray(&args, mouseButton);
|
|
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onMouseButtonUp", &args);
|
|
|
|
Toy_freeLiteral(mouseX);
|
|
Toy_freeLiteral(mouseY);
|
|
Toy_freeLiteral(mouseButton);
|
|
|
|
//hack: manual free
|
|
for(int i = 0; i < args.count; i++) {
|
|
Toy_freeLiteral(args.literals[i]);
|
|
}
|
|
args.count = 0;
|
|
}
|
|
break;
|
|
|
|
//mouse wheel
|
|
case SDL_MOUSEWHEEL: {
|
|
Toy_Literal mouseX = TOY_TO_INTEGER_LITERAL( (int)(event.wheel.x) );
|
|
Toy_Literal mouseY = TOY_TO_INTEGER_LITERAL( (int)(event.wheel.y) );
|
|
Toy_pushLiteralArray(&args, mouseX);
|
|
Toy_pushLiteralArray(&args, mouseY);
|
|
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onMouseWheel", &args);
|
|
|
|
Toy_freeLiteral(mouseX);
|
|
Toy_freeLiteral(mouseY);
|
|
|
|
//hack: manual free
|
|
for(int i = 0; i < args.count; i++) {
|
|
Toy_freeLiteral(args.literals[i]);
|
|
}
|
|
args.count = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Toy_freeLiteralArray(&args);
|
|
}
|
|
|
|
static inline void execStep() {
|
|
if (engine.rootNode != NULL) {
|
|
//steps
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onStep", NULL);
|
|
}
|
|
}
|
|
|
|
//the heart of the engine
|
|
void Box_execEngine() {
|
|
if (!engine.running) {
|
|
fatalError("Can't execute the engine (did you forget to initialize the screen?)");
|
|
}
|
|
|
|
//set up time
|
|
engine.realTime = clock();
|
|
engine.simTime = engine.realTime;
|
|
clock_t delta = (double) CLOCKS_PER_SEC / 30.0;
|
|
|
|
Dbg_Timer dbgTimer;
|
|
Dbg_FPSCounter fps;
|
|
|
|
Dbg_initTimer(&dbgTimer);
|
|
Dbg_initFPSCounter(&fps);
|
|
|
|
while (engine.running) {
|
|
Dbg_tickFPSCounter(&fps);
|
|
|
|
Dbg_clearConsole();
|
|
Dbg_printTimerLog(&dbgTimer);
|
|
Dbg_printFPSCounter(&fps);
|
|
|
|
Dbg_startTimer(&dbgTimer, "execLoadRootNode()");
|
|
execLoadRootNode();
|
|
Dbg_stopTimer(&dbgTimer);
|
|
|
|
Dbg_startTimer(&dbgTimer, "execEvents()");
|
|
execEvents();
|
|
Dbg_stopTimer(&dbgTimer);
|
|
|
|
//calc the time passed
|
|
engine.realTime = clock();
|
|
|
|
Dbg_startTimer(&dbgTimer, "execStep() (variable)");
|
|
//while not enough time has passed
|
|
while(engine.simTime < engine.realTime) {
|
|
//simulate the world
|
|
execStep();
|
|
|
|
//calc the time simulation
|
|
engine.simTime += delta;
|
|
}
|
|
Dbg_stopTimer(&dbgTimer);
|
|
|
|
//render the world
|
|
Dbg_startTimer(&dbgTimer, "clear screen");
|
|
SDL_SetRenderDrawColor(engine.renderer, 128, 128, 128, 255); //NOTE: This line can be disabled later
|
|
SDL_RenderClear(engine.renderer); //NOTE: This line can be disabled later
|
|
Dbg_stopTimer(&dbgTimer);
|
|
|
|
Dbg_startTimer(&dbgTimer, "onDraw() calls");
|
|
Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onDraw", NULL);
|
|
Dbg_stopTimer(&dbgTimer);
|
|
|
|
Dbg_startTimer(&dbgTimer, "render screen");
|
|
SDL_RenderPresent(engine.renderer);
|
|
Dbg_stopTimer(&dbgTimer);
|
|
}
|
|
|
|
Dbg_freeTimer(&dbgTimer);
|
|
Dbg_freeFPSCounter(&fps);
|
|
}
|