Basic script-based engine init is working - performance is poor

This commit is contained in:
2022-10-02 13:50:50 +01:00
parent 91afafab22
commit a7e5d101dd
9 changed files with 544 additions and 60 deletions

View File

@@ -1,66 +1,160 @@
#include "engine.h"
#include "lib_engine.h"
#include "lib_standard.h"
#include "memory.h"
#include "lexer.h"
#include "parser.h"
#include "compiler.h"
#include "interpreter.h"
#include "console_colors.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//define the engine object
Engine engine;
//errors here should be fatal
static void error(Engine* engine, char* message) {
static void fatalError(char* message) {
fprintf(stderr, message);
exit(-1);
}
//compilation functions
//TODO: move these to their own file
static char* readFile(char* path, size_t* fileSize) {
FILE* file = fopen(path, "rb");
if (file == NULL) {
fprintf(stderr, ERROR "Could not open file \"%s\"\n" RESET, path);
exit(-1);
}
fseek(file, 0L, SEEK_END);
*fileSize = ftell(file);
rewind(file);
char* buffer = (char*)malloc(*fileSize + 1);
if (buffer == NULL) {
fprintf(stderr, ERROR "Not enough memory to read \"%s\"\n" RESET, path);
exit(-1);
}
size_t bytesRead = fread(buffer, sizeof(char), *fileSize, file);
buffer[*fileSize] = '\0'; //NOTE: fread doesn't append this
if (bytesRead < *fileSize) {
fprintf(stderr, ERROR "Could not read file \"%s\"\n" RESET, path);
exit(-1);
}
fclose(file);
return buffer;
}
static unsigned char* compileString(char* source, size_t* size) {
Lexer lexer;
Parser parser;
Compiler compiler;
initLexer(&lexer, source);
initParser(&parser, &lexer);
initCompiler(&compiler);
//run the parser until the end of the source
ASTNode* node = scanParser(&parser);
while(node != NULL) {
//pack up and leave
if (node->type == AST_NODEERROR) {
printf(ERROR "error node detected\n" RESET);
freeNode(node);
freeCompiler(&compiler);
freeParser(&parser);
return NULL;
}
writeCompiler(&compiler, node);
freeNode(node);
node = scanParser(&parser);
}
//get the bytecode dump
unsigned char* tb = collateCompiler(&compiler, (int*)(size));
//cleanup
freeCompiler(&compiler);
freeParser(&parser);
//no lexer to clean up
//finally
return tb;
}
//exposed functions
void initEngine(Engine* engine) {
void initEngine() {
//clear
engine->root = NULL;
engine->running = true;
engine.rootNode = NULL;
engine.running = false;
engine.window = NULL;
engine.renderer = NULL;
//init SDL
if (SDL_Init(0) != 0) {
error(engine, "Failed to initialize SDL2");
fatalError("Failed to initialize SDL2");
}
//init the window
engine->window = SDL_CreateWindow(
"Caption",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
engine->screenWidth,
engine->screenHeight,
SDL_WINDOW_RESIZABLE
);
//init Toy
initInterpreter(&engine.interpreter);
injectNativeHook(&engine.interpreter, "engine", hookEngine);
injectNativeHook(&engine.interpreter, "standard", hookStandard);
if (engine->window == NULL) {
error(engine, "Failed to initialize the window");
}
size_t size = 0;
char* source = readFile("./assets/scripts/init.toy", &size);
unsigned char* tb = compileString(source, &size);
free((void*)source);
//init the renderer
engine->renderer = SDL_CreateRenderer(engine->window, -1, 0);
if (engine->renderer == NULL) {
error(engine, "Failed to initialize the renderer");
}
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
SDL_RenderSetLogicalSize(engine->renderer, engine->screenWidth, engine->screenHeight);
runInterpreter(&engine.interpreter, tb, size);
}
void freeEngine(Engine* engine) {
SDL_DestroyRenderer(engine->renderer);
SDL_DestroyWindow(engine->window);
void freeEngine() {
SDL_DestroyRenderer(engine.renderer);
SDL_DestroyWindow(engine.window);
SDL_Quit();
engine->renderer = NULL;
engine->window = NULL;
//clear existing root node
if (engine.rootNode != NULL) {
callEngineNode(engine.rootNode, &engine.interpreter, "onFree");
freeEngineNode(engine.rootNode);
FREE(EngineNode, engine.rootNode);
engine.rootNode = NULL;
}
freeInterpreter(&engine.interpreter);
engine.renderer = NULL;
engine.window = NULL;
}
static void execStep(Engine* engine) {
//DEBUG: for now, just poll events
static void execStep() {
//call onStep
callEngineNode(engine.rootNode, &engine.interpreter, "onStep");
//poll events
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch(event.type) {
//quit
case SDL_QUIT: {
engine->running = false;
engine.running = false;
}
break;
@@ -68,9 +162,9 @@ static void execStep(Engine* engine) {
case SDL_WINDOWEVENT: {
switch(event.window.event) {
case SDL_WINDOWEVENT_RESIZED:
engine->screenWidth = event.window.data1;
engine->screenHeight = event.window.data2;
SDL_RenderSetLogicalSize(engine->renderer, engine->screenWidth, engine->screenHeight);
engine.screenWidth = event.window.data1;
engine.screenHeight = event.window.data2;
SDL_RenderSetLogicalSize(engine.renderer, engine.screenWidth, engine.screenHeight);
break;
}
}
@@ -82,25 +176,29 @@ static void execStep(Engine* engine) {
}
//the heart of the engine
void execEngine(Engine* engine) {
void execEngine() {
if (!engine.running) {
fatalError("Can't execute the engine (did you forget to initialize the screen?)");
}
//set up time
gettimeofday(&engine->realTime, NULL);
engine->simTime = engine->realTime;
gettimeofday(&engine.realTime, NULL);
engine.simTime = engine.realTime;
struct timeval delta = { .tv_sec = 0, .tv_usec = 1000 * 1000 / 60 }; //60 frames per second
while (engine->running) {
while (engine.running) {
//calc the time passed
gettimeofday(&engine->realTime, NULL);
gettimeofday(&engine.realTime, NULL);
//if not enough time has passed
if (engine->simTime.tv_sec < engine->realTime.tv_sec && engine->simTime.tv_usec < engine->realTime.tv_usec) {
if (engine.simTime.tv_sec < engine.realTime.tv_sec && engine.simTime.tv_usec < engine.realTime.tv_usec) {
//while not enough time has passed
while(engine->simTime.tv_sec < engine->realTime.tv_sec && engine->simTime.tv_usec < engine->realTime.tv_usec) {
while(engine.simTime.tv_sec < engine.realTime.tv_sec && engine.simTime.tv_usec < engine.realTime.tv_usec) {
//simulate the world
execStep(engine);
execStep();
//calc the time simulation
timeradd(&delta, &engine->simTime, &engine->simTime);
timeradd(&delta, &engine.simTime, &engine.simTime);
}
}
else {
@@ -108,8 +206,8 @@ void execEngine(Engine* engine) {
}
//render the world
SDL_SetRenderDrawColor(engine->renderer, 0, 0, 0, 255); //NOTE: This line can be disabled later
SDL_RenderClear(engine->renderer); //NOTE: This line can be disabled later
SDL_RenderPresent(engine->renderer);
SDL_SetRenderDrawColor(engine.renderer, 0, 0, 0, 255); //NOTE: This line can be disabled later
SDL_RenderClear(engine.renderer); //NOTE: This line can be disabled later
SDL_RenderPresent(engine.renderer);
}
}

View File

@@ -2,6 +2,7 @@
#include "common.h"
#include "engine_node.h"
#include "interpreter.h"
#include <SDL2/SDL.h>
@@ -10,11 +11,14 @@
//the base engine object, which represents the state of the game
typedef struct _engine {
//engine stuff
EngineNode* root;
EngineNode* rootNode;
struct timeval simTime;
struct timeval realTime;
bool running;
//Toy stuff
Interpreter interpreter;
//SDL stuff
SDL_Window* window;
SDL_Renderer* renderer;
@@ -22,9 +26,11 @@ typedef struct _engine {
int screenHeight;
} Engine;
//APIs for initializing the engine
CORE_API void initEngine(Engine* engine);
CORE_API void freeEngine(Engine* engine);
//extern singleton
extern Engine engine;
CORE_API void execEngine(Engine* engine);
//APIs for running the engine in main()
CORE_API void initEngine();
CORE_API void execEngine();
CORE_API void freeEngine();

258
core/lib_engine.c Normal file
View File

@@ -0,0 +1,258 @@
#include "lib_engine.h"
#include "engine.h"
#include "memory.h"
#include "literal_array.h"
//errors here should be fatal
static void fatalError(char* message) {
fprintf(stderr, message);
exit(-1);
}
//compilation functions
//TODO: move these to their own file
#include "console_colors.h"
#include "lexer.h"
#include "parser.h"
#include "compiler.h"
#include "interpreter.h"
static char* readFile(char* path, size_t* fileSize) {
FILE* file = fopen(path, "rb");
if (file == NULL) {
fprintf(stderr, ERROR "Could not open file \"%s\"\n" RESET, path);
exit(-1);
}
fseek(file, 0L, SEEK_END);
*fileSize = ftell(file);
rewind(file);
char* buffer = (char*)malloc(*fileSize + 1);
if (buffer == NULL) {
fprintf(stderr, ERROR "Not enough memory to read \"%s\"\n" RESET, path);
exit(-1);
}
size_t bytesRead = fread(buffer, sizeof(char), *fileSize, file);
buffer[*fileSize] = '\0'; //NOTE: fread doesn't append this
if (bytesRead < *fileSize) {
fprintf(stderr, ERROR "Could not read file \"%s\"\n" RESET, path);
exit(-1);
}
fclose(file);
return buffer;
}
static unsigned char* compileString(char* source, size_t* size) {
Lexer lexer;
Parser parser;
Compiler compiler;
initLexer(&lexer, source);
initParser(&parser, &lexer);
initCompiler(&compiler);
//run the parser until the end of the source
ASTNode* node = scanParser(&parser);
while(node != NULL) {
//pack up and leave
if (node->type == AST_NODEERROR) {
printf(ERROR "error node detected\n" RESET);
freeNode(node);
freeCompiler(&compiler);
freeParser(&parser);
return NULL;
}
writeCompiler(&compiler, node);
freeNode(node);
node = scanParser(&parser);
}
//get the bytecode dump
unsigned char* tb = collateCompiler(&compiler, (int*)(size));
//cleanup
freeCompiler(&compiler);
freeParser(&parser);
//no lexer to clean up
//finally
return tb;
}
//native functions to be called
static int nativeInitWindow(Interpreter* interpreter, LiteralArray* arguments) {
if (engine.window != NULL) {
fatalError("Can't re-initialize the window\n");
}
if (arguments->count != 4) {
fatalError("Incorrect number of arguments passed to initEngine\n");
}
//extract the arguments
Literal fscreen = popLiteralArray(arguments);
Literal screenHeight = popLiteralArray(arguments);
Literal screenWidth = popLiteralArray(arguments);
Literal caption = popLiteralArray(arguments);
//check argument types
if (!IS_STRING(caption) || !IS_INTEGER(screenWidth) || !IS_INTEGER(screenHeight) || !IS_BOOLEAN(fscreen)) {
fatalError("Incorrect argument type passed to initEngine\n");
}
//init the window
engine.window = SDL_CreateWindow(
AS_STRING(caption),
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
engine.screenWidth = AS_INTEGER(screenWidth),
engine.screenHeight = AS_INTEGER(screenHeight),
IS_TRUTHY(fscreen) ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_RESIZABLE
);
if (engine.window == NULL) {
fatalError("Failed to initialize the window\n");
}
//init the renderer
engine.renderer = SDL_CreateRenderer(engine.window, -1, 0);
if (engine.renderer == NULL) {
fatalError("Failed to initialize the renderer\n");
}
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
SDL_RenderSetLogicalSize(engine.renderer, engine.screenWidth, engine.screenHeight);
//only run with a window
engine.running = true;
return 0;
}
static int nativeLoadRootNode(Interpreter* interpreter, LiteralArray* arguments) {
if (arguments->count != 1) {
interpreter->errorOutput("Incorrect number of arguments passed to loadRootNode\n");
return -1;
}
//extract the arguments
Literal fname = popLiteralArray(arguments);
//check argument types
if (!IS_STRING(fname)) {
interpreter->errorOutput("Incorrect argument type passed to loadRootNode\n");
freeLiteral(fname);
return -1;
}
//clear existing root node
if (engine.rootNode != NULL) {
callEngineNode(engine.rootNode, &engine.interpreter, "onFree");
freeEngineNode(engine.rootNode);
FREE(EngineNode, engine.rootNode);
engine.rootNode = NULL;
}
//load the new root node
size_t size = 0;
char* source = readFile(AS_STRING(fname), &size);
unsigned char* tb = compileString(source, &size);
free((void*)source);
engine.rootNode = ALLOCATE(EngineNode, 1);
//BUGFIX: use an inner-interpreter here, otherwise it'll mess up the original's length value by calling run within a native function
Interpreter inner;
initInterpreter(&inner);
initEngineNode(engine.rootNode, &inner, tb, size);
freeInterpreter(&inner);
//init the new node
callEngineNode(engine.rootNode, &engine.interpreter, "onInit");
//cleanup
freeLiteral(fname);
return 0;
}
//call the hook
typedef struct Natives {
char* name;
NativeFn fn;
} Natives;
int hookEngine(Interpreter* interpreter, Literal identifier, Literal alias) {
//build the natives list
Natives natives[] = {
{"initWindow", nativeInitWindow},
{"loadRootNode", nativeLoadRootNode},
{NULL, NULL}
};
//store the library in an aliased dictionary
if (!IS_NULL(alias)) {
//make sure the name isn't taken
if (isDelcaredScopeVariable(interpreter->scope, alias)) {
interpreter->errorOutput("Can't override an existing variable\n");
freeLiteral(alias);
return false;
}
//create the dictionary to load up with functions
LiteralDictionary* dictionary = ALLOCATE(LiteralDictionary, 1);
initLiteralDictionary(dictionary);
//load the dict with functions
for (int i = 0; natives[i].name; i++) {
Literal name = TO_STRING_LITERAL(copyString(natives[i].name, strlen(natives[i].name)), strlen(natives[i].name));
Literal func = TO_FUNCTION_LITERAL((void*)natives[i].fn, 0);
func.type = LITERAL_FUNCTION_NATIVE;
setLiteralDictionary(dictionary, name, func);
freeLiteral(name);
freeLiteral(func);
}
//build the type
Literal type = TO_TYPE_LITERAL(LITERAL_DICTIONARY, true);
Literal strType = TO_TYPE_LITERAL(LITERAL_STRING, true);
Literal fnType = TO_TYPE_LITERAL(LITERAL_FUNCTION_NATIVE, true);
TYPE_PUSH_SUBTYPE(&type, strType);
TYPE_PUSH_SUBTYPE(&type, fnType);
//set scope
Literal dict = TO_DICTIONARY_LITERAL(dictionary);
declareScopeVariable(interpreter->scope, alias, type);
setScopeVariable(interpreter->scope, alias, dict, false);
//cleanup
freeLiteral(dict);
freeLiteral(type);
return 0;
}
//default
for (int i = 0; natives[i].name; i++) {
injectNativeFn(interpreter, natives[i].name, natives[i].fn);
}
return 0;
}

6
core/lib_engine.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include "interpreter.h"
int hookEngine(Interpreter* interpreter, Literal identifier, Literal alias);

94
core/lib_standard.c Normal file
View File

@@ -0,0 +1,94 @@
#include "lib_standard.h"
#include "memory.h"
#include <time.h>
static int nativeClock(Interpreter* interpreter, LiteralArray* arguments) {
//no arguments
if (arguments->count != 0) {
interpreter->errorOutput("Incorrect number of arguments to clock\n");
return -1;
}
//get the time from C (what a pain)
time_t rawtime = time(NULL);
struct tm* timeinfo = localtime( &rawtime );
char* timestr = asctime(timeinfo);
//push to the stack
int len = strlen(timestr) - 1; //-1 for the newline
Literal timeLiteral = TO_STRING_LITERAL(copyString(timestr, len), len);
//push to the stack
pushLiteralArray(&interpreter->stack, timeLiteral);
//cleanup
freeLiteral(timeLiteral);
return 1;
}
//call the hook
typedef struct Natives {
char* name;
NativeFn fn;
} Natives;
int hookStandard(Interpreter* interpreter, Literal identifier, Literal alias) {
//build the natives list
Natives natives[] = {
{"clock", nativeClock},
{NULL, NULL}
};
//store the library in an aliased dictionary
if (!IS_NULL(alias)) {
//make sure the name isn't taken
if (isDelcaredScopeVariable(interpreter->scope, alias)) {
interpreter->errorOutput("Can't override an existing variable\n");
freeLiteral(alias);
return false;
}
//create the dictionary to load up with functions
LiteralDictionary* dictionary = ALLOCATE(LiteralDictionary, 1);
initLiteralDictionary(dictionary);
//load the dict with functions
for (int i = 0; natives[i].name; i++) {
Literal name = TO_STRING_LITERAL(copyString(natives[i].name, strlen(natives[i].name)), strlen(natives[i].name));
Literal func = TO_FUNCTION_LITERAL((void*)natives[i].fn, 0);
func.type = LITERAL_FUNCTION_NATIVE;
setLiteralDictionary(dictionary, name, func);
freeLiteral(name);
freeLiteral(func);
}
//build the type
Literal type = TO_TYPE_LITERAL(LITERAL_DICTIONARY, true);
Literal strType = TO_TYPE_LITERAL(LITERAL_STRING, true);
Literal fnType = TO_TYPE_LITERAL(LITERAL_FUNCTION_NATIVE, true);
TYPE_PUSH_SUBTYPE(&type, strType);
TYPE_PUSH_SUBTYPE(&type, fnType);
//set scope
Literal dict = TO_DICTIONARY_LITERAL(dictionary);
declareScopeVariable(interpreter->scope, alias, type);
setScopeVariable(interpreter->scope, alias, dict, false);
//cleanup
freeLiteral(dict);
freeLiteral(type);
return 0;
}
//default
for (int i = 0; natives[i].name; i++) {
injectNativeFn(interpreter, natives[i].name, natives[i].fn);
}
return 0;
}

6
core/lib_standard.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#include "interpreter.h"
int hookStandard(Interpreter* interpreter, Literal identifier, Literal alias);