#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 #include #include //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, "draw screen"); Box_callRecursiveEngineNode(engine.rootNode, &engine.interpreter, "onDraw", NULL); SDL_RenderPresent(engine.renderer); Dbg_stopTimer(&dbgTimer); } Dbg_freeTimer(&dbgTimer); Dbg_freeFPSCounter(&fps); }