Add module for Scheduler.

Also reorganizes some code to make it easier to add more modules.
This commit is contained in:
Bob Nystrom
2015-09-13 11:32:39 -07:00
parent f36a795534
commit ea5c3b01eb
18 changed files with 291 additions and 110 deletions

View File

@ -8,6 +8,11 @@
/* Begin PBXBuildFile section */
2901D7641B74F4050083A2C8 /* timer.c in Sources */ = {isa = PBXBuildFile; fileRef = 2901D7621B74F4050083A2C8 /* timer.c */; };
291647C41BA5EA45006142EE /* scheduler.c in Sources */ = {isa = PBXBuildFile; fileRef = 291647C21BA5EA45006142EE /* scheduler.c */; };
291647C71BA5EC5E006142EE /* modules.c in Sources */ = {isa = PBXBuildFile; fileRef = 291647C51BA5EC5E006142EE /* modules.c */; };
291647C81BA5EC5E006142EE /* modules.c in Sources */ = {isa = PBXBuildFile; fileRef = 291647C51BA5EC5E006142EE /* modules.c */; };
291647D01BA5ED26006142EE /* scheduler.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 291647CD1BA5ED26006142EE /* scheduler.wren.inc */; };
291647D21BA5ED26006142EE /* timer.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 291647CE1BA5ED26006142EE /* timer.wren.inc */; };
29205C8F1AB4E5C90073018D /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C8E1AB4E5C90073018D /* main.c */; };
29205C991AB4E6430073018D /* wren_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C921AB4E6430073018D /* wren_compiler.c */; };
29205C9A1AB4E6430073018D /* wren_core.c in Sources */ = {isa = PBXBuildFile; fileRef = 29205C931AB4E6430073018D /* wren_core.c */; };
@ -48,6 +53,12 @@
/* Begin PBXFileReference section */
2901D7621B74F4050083A2C8 /* timer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = timer.c; path = ../../src/module/timer.c; sourceTree = "<group>"; };
2901D7631B74F4050083A2C8 /* timer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = timer.h; path = ../../src/module/timer.h; sourceTree = "<group>"; };
291647C21BA5EA45006142EE /* scheduler.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = scheduler.c; path = ../../src/module/scheduler.c; sourceTree = "<group>"; };
291647C31BA5EA45006142EE /* scheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = scheduler.h; path = ../../src/module/scheduler.h; sourceTree = "<group>"; };
291647C51BA5EC5E006142EE /* modules.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = modules.c; path = ../../src/cli/modules.c; sourceTree = "<group>"; };
291647C61BA5EC5E006142EE /* modules.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = modules.h; path = ../../src/cli/modules.h; sourceTree = "<group>"; };
291647CD1BA5ED26006142EE /* scheduler.wren.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; name = scheduler.wren.inc; path = ../../src/module/scheduler.wren.inc; sourceTree = "<group>"; };
291647CE1BA5ED26006142EE /* timer.wren.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; name = timer.wren.inc; path = ../../src/module/timer.wren.inc; sourceTree = "<group>"; };
29205C8E1AB4E5C90073018D /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = main.c; path = ../../src/cli/main.c; sourceTree = "<group>"; };
29205C901AB4E62B0073018D /* wren.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren.h; path = ../../src/include/wren.h; sourceTree = "<group>"; };
29205C921AB4E6430073018D /* wren_compiler.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_compiler.c; path = ../../src/vm/wren_compiler.c; sourceTree = "<group>"; };
@ -109,8 +120,12 @@
2901D7611B74F3E20083A2C8 /* module */ = {
isa = PBXGroup;
children = (
291647C31BA5EA45006142EE /* scheduler.h */,
291647C21BA5EA45006142EE /* scheduler.c */,
291647CD1BA5ED26006142EE /* scheduler.wren.inc */,
2901D7631B74F4050083A2C8 /* timer.h */,
2901D7621B74F4050083A2C8 /* timer.c */,
291647CE1BA5ED26006142EE /* timer.wren.inc */,
);
name = module;
sourceTree = "<group>";
@ -148,6 +163,8 @@
29C8A9301AB71C3300DEC81D /* io.h */,
29C8A92E1AB71C1C00DEC81D /* io.c */,
29205C8E1AB4E5C90073018D /* main.c */,
291647C61BA5EC5E006142EE /* modules.h */,
291647C51BA5EC5E006142EE /* modules.c */,
29C8A9321AB71FFF00DEC81D /* vm.h */,
29C8A9311AB71FFF00DEC81D /* vm.c */,
);
@ -274,9 +291,11 @@
files = (
29205C991AB4E6430073018D /* wren_compiler.c in Sources */,
2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */,
291647C71BA5EC5E006142EE /* modules.c in Sources */,
29205C9A1AB4E6430073018D /* wren_core.c in Sources */,
2901D7641B74F4050083A2C8 /* timer.c in Sources */,
29C8A9331AB71FFF00DEC81D /* vm.c in Sources */,
291647C41BA5EA45006142EE /* scheduler.c in Sources */,
29205C9B1AB4E6430073018D /* wren_debug.c in Sources */,
29205C9C1AB4E6430073018D /* wren_io.c in Sources */,
29205C9D1AB4E6430073018D /* wren_utils.c in Sources */,
@ -292,6 +311,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
291647C81BA5EC5E006142EE /* modules.c in Sources */,
291647D21BA5ED26006142EE /* timer.wren.inc in Sources */,
291647D01BA5ED26006142EE /* scheduler.wren.inc in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -379,7 +401,7 @@
GCC_WARN_PEDANTIC = NO;
LIBRARY_SEARCH_PATHS = ../../build;
PRODUCT_NAME = "$(TARGET_NAME)";
USER_HEADER_SEARCH_PATHS = ../../deps/libuv/include;
USER_HEADER_SEARCH_PATHS = "../../deps/libuv/include ../../src/module";
};
name = Debug;
};
@ -392,7 +414,7 @@
GCC_WARN_PEDANTIC = NO;
LIBRARY_SEARCH_PATHS = ../../build;
PRODUCT_NAME = "$(TARGET_NAME)";
USER_HEADER_SEARCH_PATHS = ../../deps/libuv/include;
USER_HEADER_SEARCH_PATHS = "../../deps/libuv/include ../../src/module";
};
name = Release;
};
@ -416,7 +438,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
USER_HEADER_SEARCH_PATHS = ../../deps/libuv/include;
USER_HEADER_SEARCH_PATHS = "../../deps/libuv/include ../../src/module";
};
name = Debug;
};
@ -436,7 +458,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.10;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_NAME = "$(TARGET_NAME)";
USER_HEADER_SEARCH_PATHS = ../../deps/libuv/include;
USER_HEADER_SEARCH_PATHS = "../../deps/libuv/include ../../src/module";
};
name = Release;
};

View File

@ -153,7 +153,8 @@ lib/lib$(WREN).$(SHARED_EXT): $(VM_OBJECTS)
# Test executable.
$(BUILD_DIR)/test/$(WREN): $(TEST_OBJECTS) $(MODULE_OBJECTS) $(VM_OBJECTS) \
$(BUILD_DIR)/cli/io.o $(BUILD_DIR)/cli/vm.o $(LIBUV)
$(BUILD_DIR)/cli/io.o $(BUILD_DIR)/cli/modules.o $(BUILD_DIR)/cli/vm.o \
$(LIBUV)
@ printf "%10s %-30s %s\n" $(CC) $@ "$(C_OPTIONS)"
@ mkdir -p $(BUILD_DIR)/test
@ $(CC) $(CFLAGS) $^ -o $@ -lm $(LIBUV_LIBS)

View File

@ -3,8 +3,7 @@
#include <string.h>
#include "io.h"
#include "timer.h"
#include "modules.h"
char const* rootDirectory = NULL;
@ -73,7 +72,8 @@ char* wrenFilePath(const char* name)
char* readModule(WrenVM* vm, const char* module)
{
if (strcmp(module, "timer") == 0) return timerGetSource();
char* source = readBuiltInModule(module);
if (source != NULL) return source;
// First try to load the module with a ".wren" extension.
char* modulePath = wrenFilePath(module);

83
src/cli/modules.c Normal file
View File

@ -0,0 +1,83 @@
#include <stdlib.h>
#include <string.h>
#include "modules.h"
#include "scheduler.wren.inc"
#include "timer.wren.inc"
#include "scheduler.h"
#include "timer.h"
typedef struct
{
// The name of the module.
const char* name;
// Pointer to the string containing the source code of the module. We use a
// pointer here because the string variable itself is not a constant
// expression so can't be used in the initializer below.
const char **source;
// The function that binds foreign methods in this module.
WrenForeignMethodFn (*bindMethodFn)(WrenVM* vm, const char* className,
bool isStatic, const char* signature);
// The function that binds foreign classes in this module.
WrenForeignClassMethods (*bindClassFn)(WrenVM* vm, const char* className);
} BuiltInModule;
// The array of built-in modules.
static BuiltInModule modules[] =
{
{"scheduler", &schedulerModuleSource, schedulerBindForeign, NULL},
{"timer", &timerModuleSource, timerBindForeign, NULL},
// Sentinel marking the end of the list.
{NULL, NULL, NULL, NULL}
};
// Looks for a built-in module with [name].
//
// Returns the BuildInModule for it or NULL if not found.
static BuiltInModule* findModule(const char* name)
{
for (int i = 0; modules[i].name != NULL; i++)
{
if (strcmp(name, modules[i].name) == 0) return &modules[i];
}
return NULL;
}
char* readBuiltInModule(const char* name)
{
BuiltInModule* module = findModule(name);
if (module == NULL) return NULL;
size_t length = strlen(*module->source);
char* copy = (char*)malloc(length + 1);
strncpy(copy, *module->source, length + 1);
return copy;
}
WrenForeignMethodFn bindBuiltInForeignMethod(
WrenVM* vm, const char* moduleName, const char* className, bool isStatic,
const char* signature)
{
BuiltInModule* module = findModule(moduleName);
if (module == NULL) return NULL;
return module->bindMethodFn(vm, className, isStatic, signature);
}
WrenForeignClassMethods bindBuiltInForeignClass(
WrenVM* vm, const char* moduleName, const char* className)
{
WrenForeignClassMethods methods = { NULL, NULL };
BuiltInModule* module = findModule(moduleName);
if (module == NULL) return methods;
return module->bindClassFn(vm, className);
}

19
src/cli/modules.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef modules_h
#define modules_h
#include "wren.h"
char* readBuiltInModule(const char* module);
// Looks up a foreign method in a built-in module.
//
// Returns `NULL` if [moduleName] is not a built-in module.
WrenForeignMethodFn bindBuiltInForeignMethod(
WrenVM* vm, const char* moduleName, const char* className, bool isStatic,
const char* signature);
// Binds foreign classes declared in a built-in modules.
WrenForeignClassMethods bindBuiltInForeignClass(
WrenVM* vm, const char* moduleName, const char* className);
#endif

View File

@ -2,8 +2,9 @@
#include <string.h>
#include "io.h"
#include "modules.h"
#include "vm.h"
#include "timer.h"
#include "scheduler.h"
#define MAX_LINE_LENGTH 1024 // TODO: Something less arbitrary.
@ -15,16 +16,15 @@ static WrenBindForeignClassFn bindClassFn = NULL;
uv_loop_t* loop;
/// Binds foreign methods declared in either built in modules, or the injected
/// API test modules.
// Binds foreign methods declared in either built in modules, or the injected
// API test modules.
static WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module,
const char* className, bool isStatic, const char* signature)
{
if (strcmp(module, "timer") == 0)
{
return timerBindForeign(vm, className, isStatic, signature);
}
WrenForeignMethodFn method = bindBuiltInForeignMethod(vm, module, className,
isStatic, signature);
if (method != NULL) return method;
if (bindMethodFn != NULL)
{
return bindMethodFn(vm, module, className, isStatic, signature);
@ -33,14 +33,14 @@ static WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module,
return NULL;
}
/// Binds foreign classes declared in either built in modules, or the injected
/// API test modules.
// Binds foreign classes declared in either built in modules, or the injected
// API test modules.
static WrenForeignClassMethods bindForeignClass(
WrenVM* vm, const char* module, const char* className)
{
WrenForeignClassMethods methods = { NULL, NULL };
// TODO: Bind classes for built-in modules here.
WrenForeignClassMethods methods = bindBuiltInForeignClass(vm, module,
className);
if (methods.allocate != NULL) return methods;
if (bindClassFn != NULL)
{
@ -70,7 +70,7 @@ static void initVM()
static void freeVM()
{
timerReleaseMethods();
schedulerReleaseMethods();
uv_loop_close(loop);
free(loop);

42
src/module/scheduler.c Normal file
View File

@ -0,0 +1,42 @@
#include <stdlib.h>
#include <string.h>
#include "uv.h"
#include "scheduler.h"
#include "wren.h"
#include "vm.h"
// This method resumes a fiber that is suspended waiting on an asynchronous
// operation. The first resumes it with zero arguments, and the second passes
// one.
static WrenValue* resume;
static WrenValue* resumeWithArg;
static void captureMethods(WrenVM* vm)
{
resume = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_)");
resumeWithArg = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_,_)");
}
WrenForeignMethodFn schedulerBindForeign(
WrenVM* vm, const char* className, bool isStatic, const char* signature)
{
if (strcmp(className, "Scheduler") != 0) return NULL;
if (isStatic && strcmp(signature, "captureMethods_()") == 0) return captureMethods;
return NULL;
}
void schedulerResume(WrenValue* fiber)
{
wrenCall(getVM(), resume, NULL, "v", fiber);
wrenReleaseValue(getVM(), fiber);
}
void schedulerReleaseMethods()
{
if (resume != NULL) wrenReleaseValue(getVM(), resume);
if (resumeWithArg != NULL) wrenReleaseValue(getVM(), resumeWithArg);
}

13
src/module/scheduler.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef scheduler_h
#define scheduler_h
#include "wren.h"
WrenForeignMethodFn schedulerBindForeign(
WrenVM* vm, const char* className, bool isStatic, const char* signature);
void schedulerResume(WrenValue* fiber);
void schedulerReleaseMethods();
#endif

26
src/module/scheduler.wren Normal file
View File

@ -0,0 +1,26 @@
class Scheduler {
static add(callable) {
if (__scheduled == null) __scheduled = []
__scheduled.add(Fiber.new {
callable.call()
runNextScheduled_()
})
}
// Called by native code.
static resume_(fiber) { fiber.transfer() }
static resume_(fiber, arg) { fiber.transfer(arg) }
static runNextScheduled_() {
if (__scheduled == null || __scheduled.isEmpty) {
return Fiber.suspend()
} else {
return __scheduled.removeAt(0).transfer()
}
}
foreign static captureMethods_()
}
Scheduler.captureMethods_()

View File

@ -0,0 +1,28 @@
// Generated automatically from src/module/scheduler.wren. Do not edit.
static const char* schedulerModuleSource =
"class Scheduler {\n"
" static add(callable) {\n"
" if (__scheduled == null) __scheduled = []\n"
"\n"
" __scheduled.add(Fiber.new {\n"
" callable.call()\n"
" runNextScheduled_()\n"
" })\n"
" }\n"
"\n"
" // Called by native code.\n"
" static resume_(fiber) { fiber.transfer() }\n"
" static resume_(fiber, arg) { fiber.transfer(arg) }\n"
"\n"
" static runNextScheduled_() {\n"
" if (__scheduled == null || __scheduled.isEmpty) {\n"
" return Fiber.suspend()\n"
" } else {\n"
" return __scheduled.removeAt(0).transfer()\n"
" }\n"
" }\n"
"\n"
" foreign static captureMethods_()\n"
"}\n"
"\n"
"Scheduler.captureMethods_()\n";

View File

@ -3,14 +3,10 @@
#include "uv.h"
#include "scheduler.h"
#include "timer.h"
#include "wren.h"
#include "vm.h"
#include "timer.wren.inc"
// The Wren method to call when a timer has completed.
static WrenValue* resumeTimer;
#include "wren.h"
// Called by libuv when the timer finished closing.
static void timerCloseCallback(uv_handle_t* handle)
@ -27,18 +23,11 @@ static void timerCallback(uv_timer_t* handle)
uv_close((uv_handle_t*)handle, timerCloseCallback);
// Run the fiber that was sleeping.
wrenCall(getVM(), resumeTimer, NULL, "v", fiber);
wrenReleaseValue(getVM(), fiber);
schedulerResume(fiber);
}
static void startTimer(WrenVM* vm)
{
// If we haven't looked up the resume method yet, grab it now.
if (resumeTimer == NULL)
{
resumeTimer = wrenGetMethod(vm, "timer", "Timer", "resumeTimer_(_)");
}
int milliseconds = (int)wrenGetArgumentDouble(vm, 1);
WrenValue* fiber = wrenGetArgumentValue(vm, 2);
@ -50,15 +39,6 @@ static void startTimer(WrenVM* vm)
uv_timer_start(handle, timerCallback, milliseconds, 0);
}
char* timerGetSource()
{
size_t length = strlen(timerModuleSource);
char* copy = (char*)malloc(length + 1);
strncpy(copy, timerModuleSource, length + 1);
return copy;
}
WrenForeignMethodFn timerBindForeign(
WrenVM* vm, const char* className, bool isStatic, const char* signature)
{
@ -68,8 +48,3 @@ WrenForeignMethodFn timerBindForeign(
return NULL;
}
void timerReleaseMethods()
{
if (resumeTimer != NULL) wrenReleaseValue(getVM(), resumeTimer);
}

View File

@ -3,11 +3,7 @@
#include "wren.h"
char* timerGetSource();
WrenForeignMethodFn timerBindForeign(
WrenVM* vm, const char* className, bool isStatic, const char* signature);
void timerReleaseMethods();
#endif

View File

@ -1,34 +1,13 @@
import "scheduler" for Scheduler
class Timer {
static sleep(milliseconds) {
if (!(milliseconds is Num)) Fiber.abort("Milliseconds must be a number.")
if (milliseconds < 0) Fiber.abort("Milliseconds cannot be negative.")
startTimer_(milliseconds, Fiber.current)
runNextScheduled_()
}
// TODO: Once the CLI modules are more fleshed out, find a better place to
// put this.
static schedule(callable) {
if (__scheduled == null) __scheduled = []
__scheduled.add(Fiber.new {
callable.call()
runNextScheduled_()
})
Scheduler.runNextScheduled_()
}
foreign static startTimer_(milliseconds, fiber)
// Called by native code.
static resumeTimer_(fiber) {
fiber.transfer()
}
static runNextScheduled_() {
if (__scheduled == null || __scheduled.isEmpty) {
Fiber.suspend()
} else {
__scheduled.removeAt(0).transfer()
}
}
}

View File

@ -1,36 +1,15 @@
// Generated automatically from src/module/timer.wren. Do not edit.
static const char* timerModuleSource =
"import \"scheduler\" for Scheduler\n"
"\n"
"class Timer {\n"
" static sleep(milliseconds) {\n"
" if (!(milliseconds is Num)) Fiber.abort(\"Milliseconds must be a number.\")\n"
" if (milliseconds < 0) Fiber.abort(\"Milliseconds cannot be negative.\")\n"
"\n"
" startTimer_(milliseconds, Fiber.current)\n"
"\n"
" runNextScheduled_()\n"
" }\n"
"\n"
" // TODO: Once the CLI modules are more fleshed out, find a better place to\n"
" // put this.\n"
" static schedule(callable) {\n"
" if (__scheduled == null) __scheduled = []\n"
" __scheduled.add(Fiber.new {\n"
" callable.call()\n"
" runNextScheduled_()\n"
" })\n"
" Scheduler.runNextScheduled_()\n"
" }\n"
"\n"
" foreign static startTimer_(milliseconds, fiber)\n"
"\n"
" // Called by native code.\n"
" static resumeTimer_(fiber) {\n"
" fiber.transfer()\n"
" }\n"
"\n"
" static runNextScheduled_() {\n"
" if (__scheduled == null || __scheduled.isEmpty) {\n"
" Fiber.suspend()\n"
" } else {\n"
" __scheduled.removeAt(0).transfer()\n"
" }\n"
" }\n"
"}\n";

15
test/scheduler/add.wren Normal file
View File

@ -0,0 +1,15 @@
import "scheduler" for Scheduler
import "timer" for Timer
var run = false
Scheduler.add {
run = true
}
// Does not run immediately.
IO.print(run) // expect: false
Timer.sleep(0)
// Runs when the main fiber suspends on IO.
IO.print(run) // expect: true

View File

@ -1,11 +1,12 @@
import "scheduler" for Scheduler
import "timer" for Timer
Timer.schedule {
Scheduler.add {
Timer.sleep(2)
IO.print("a")
}
Timer.schedule {
Scheduler.add {
Timer.sleep(1)
IO.print("b")
}

View File

@ -1,12 +1,13 @@
import "scheduler" for Scheduler
import "timer" for Timer
// These are both rounded to 1, so "a" should complete first.
Timer.schedule {
Scheduler.add {
Timer.sleep(1.5)
IO.print("a")
}
Timer.schedule {
Scheduler.add {
Timer.sleep(1.3)
IO.print("b")
}

View File

@ -1,12 +1,13 @@
import "scheduler" for Scheduler
import "timer" for Timer
Timer.schedule {
Scheduler.add {
IO.print("a before")
Timer.sleep(0)
IO.print("a after")
}
Timer.schedule {
Scheduler.add {
IO.print("b before")
Timer.sleep(0)
IO.print("b after")