aboutsummaryrefslogtreecommitdiff
path: root/src/hotload.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/hotload.c')
-rw-r--r--src/hotload.c288
1 files changed, 212 insertions, 76 deletions
diff --git a/src/hotload.c b/src/hotload.c
index ad02d21..88d286a 100644
--- a/src/hotload.c
+++ b/src/hotload.c
@@ -8,23 +8,59 @@
#define FSEVENT_CALLBACK(name) void name(ConstFSEventStreamRef stream,\
void *context,\
- size_t count,\
- void *paths,\
+ size_t file_count,\
+ void *file_paths,\
const FSEventStreamEventFlags *flags,\
const FSEventStreamEventId *ids)
+enum watch_kind
+{
+ WATCH_KIND_INVALID,
+ WATCH_KIND_CATALOG,
+ WATCH_KIND_FILE
+};
+
+struct watched_catalog
+{
+ char *directory;
+ char *extension;
+};
+
+struct watched_file
+{
+ char *absolutepath;
+ char *directory;
+ char *filename;
+};
+
+struct watched_entry
+{
+ enum watch_kind kind;
+ union {
+ struct watched_file file_info;
+ struct watched_catalog catalog_info;
+ };
+};
+
+internal inline bool
+same_string(const char *a, const char *b)
+{
+ bool result = a && b && strcmp(a, b) == 0;
+ return result;
+}
+
internal char *
copy_string(const char *s)
{
unsigned length = strlen(s);
- char *result = malloc(length + 1);
+ char *result = (char *) malloc(length + 1);
memcpy(result, s, length);
result[length] = '\0';
return result;
}
internal char *
-file_directory(const char *file)
+file_directory(char *file)
{
char *last_slash = strrchr(file, '/');
*last_slash = '\0';
@@ -34,7 +70,7 @@ file_directory(const char *file)
}
internal char *
-file_name(const char *file)
+file_name(char *file)
{
char *last_slash = strrchr(file, '/');
char *name = copy_string(last_slash + 1);
@@ -42,50 +78,73 @@ file_name(const char *file)
}
internal char *
-resolve_symlink(char *file)
+resolve_symlink(const char *file)
{
struct stat buffer;
if (lstat(file, &buffer) != 0) {
return NULL;
}
+ if (S_ISDIR(buffer.st_mode)) {
+ return copy_string(file);
+ }
+
if (!S_ISLNK(buffer.st_mode)) {
- return file;
+ return copy_string(file);
}
- ssize_t size = buffer.st_size + 1;
- char *result = malloc(size);
- ssize_t read = readlink(file, result, size);
+ char *result = realpath(file, NULL);
+ return result;
+}
- if (read != -1) {
- result[read] = '\0';
- return result;
+internal enum watch_kind
+resolve_watch_kind(char *file)
+{
+ struct stat buffer;
+ if (lstat(file, &buffer) != 0) {
+ return WATCH_KIND_INVALID;
}
- free(result);
- return NULL;
+ if (S_ISDIR(buffer.st_mode)) {
+ return WATCH_KIND_CATALOG;
+ }
+
+ if (!S_ISLNK(buffer.st_mode)) {
+ return WATCH_KIND_FILE;
+ }
+
+ return WATCH_KIND_INVALID;
}
-internal struct watched_file *
-hotloader_watched_file(struct hotloader *hotloader, char *absolutepath)
+internal char *
+same_catalog(char *absolutepath, struct watched_catalog *catalog_info)
{
- struct watched_file *result = NULL;
- for (unsigned index = 0; result == NULL && index < hotloader->watch_count; ++index) {
- struct watched_file *watch_info = hotloader->watch_list + index;
+ char *last_slash = strrchr(absolutepath, '/');
+ if (!last_slash) return NULL;
- char *directory = file_directory(absolutepath);
- char *filename = file_name(absolutepath);
+ char *filename = NULL;
- if (strcmp(watch_info->directory, directory) == 0) {
- if (strcmp(watch_info->filename, filename) == 0) {
- result = watch_info;
- }
- }
+ // NOTE(koekeisihya): null terminate '/' to cut off filename
+ *last_slash = '\0';
- free(filename);
- free(directory);
+ if (same_string(absolutepath, catalog_info->directory)) {
+ filename = !catalog_info->extension
+ ? last_slash + 1
+ : same_string(catalog_info->extension, strrchr(last_slash + 1, '.'))
+ ? last_slash + 1
+ : NULL;
}
+ // NOTE(koekeisihya): revert '/' to restore filename
+ *last_slash = '/';
+
+ return filename;
+}
+
+internal inline bool
+same_file(char *absolutepath, struct watched_file *file_info)
+{
+ bool result = same_string(absolutepath, file_info->absolutepath);
return result;
}
@@ -93,59 +152,120 @@ internal FSEVENT_CALLBACK(hotloader_handler)
{
/* NOTE(koekeishiya): We sometimes get two events upon file save. */
struct hotloader *hotloader = (struct hotloader *) context;
- char **files = (char **) paths;
+ char **files = (char **) file_paths;
- struct watched_file *watch_info;
- for (unsigned index = 0; index < count; ++index) {
- char *absolutepath = files[index];
- if ((watch_info = hotloader_watched_file(hotloader, absolutepath))) {
- hotloader->callback(absolutepath, watch_info->directory, watch_info->filename);
+ for (unsigned file_index = 0; file_index < file_count; ++file_index) {
+ for (unsigned watch_index = 0; watch_index < hotloader->watch_count; ++watch_index) {
+ struct watched_entry *watch_info = hotloader->watch_list + watch_index;
+ if (watch_info->kind == WATCH_KIND_CATALOG) {
+ char *filename = same_catalog(files[file_index], &watch_info->catalog_info);
+ if (!filename) continue;
+
+ hotloader->callback(files[file_index],
+ watch_info->catalog_info.directory,
+ filename);
+ break;
+ } else if (watch_info->kind == WATCH_KIND_FILE) {
+ bool match = same_file(files[file_index], &watch_info->file_info);
+ if (!match) continue;
+
+ hotloader->callback(watch_info->file_info.absolutepath,
+ watch_info->file_info.directory,
+ watch_info->file_info.filename);
+ break;
+ }
}
}
}
-void hotloader_add_file(struct hotloader *hotloader, char *file)
+internal inline void
+hotloader_add_watched_entry(struct hotloader *hotloader, struct watched_entry entry)
{
- if (!hotloader->enabled) {
- char *real_path = resolve_symlink(file);
- if (real_path) {
- struct watched_file watch_info;
- watch_info.directory = file_directory(real_path);
- watch_info.filename = file_name(real_path);
+ if (!hotloader->watch_list) {
+ hotloader->watch_capacity = 32;
+ hotloader->watch_list = (struct watched_entry *) malloc(hotloader->watch_capacity * sizeof(struct watched_entry));
+ }
- if (real_path != file) {
- free(real_path);
- }
+ if (hotloader->watch_count >= hotloader->watch_capacity) {
+ hotloader->watch_capacity = (unsigned) ceil(hotloader->watch_capacity * 1.5f);
+ hotloader->watch_list = (struct watched_entry *) realloc(hotloader->watch_list, hotloader->watch_capacity * sizeof(struct watched_entry));
+ }
+
+ hotloader->watch_list[hotloader->watch_count++] = entry;
+}
- hotloader->watch_list[hotloader->watch_count++] = watch_info;
- printf("hotload: watching file '%s' in directory '%s'\n", watch_info.filename, watch_info.directory);
- } else {
- fprintf(stderr, "hotload: could not watch file '%s'\n", file);
+bool hotloader_add_catalog(struct hotloader *hotloader, const char *directory, const char *extension)
+{
+ if (hotloader->enabled) return false;
+
+ char *real_path = resolve_symlink(directory);
+ if (!real_path) return false;
+
+ enum watch_kind kind = resolve_watch_kind(real_path);
+ if (kind != WATCH_KIND_CATALOG) return false;
+
+ hotloader_add_watched_entry(hotloader, (struct watched_entry) {
+ .kind = WATCH_KIND_CATALOG,
+ .catalog_info = {
+ .directory = real_path,
+ .extension = extension
+ ? copy_string(extension)
+ : NULL
}
- }
+ });
+
+ return true;
+}
+
+bool hotloader_add_file(struct hotloader *hotloader, const char *file)
+{
+ if (hotloader->enabled) return false;
+
+ char *real_path = resolve_symlink(file);
+ if (!real_path) return false;
+
+ enum watch_kind kind = resolve_watch_kind(real_path);
+ if (kind != WATCH_KIND_FILE) return false;
+
+ hotloader_add_watched_entry(hotloader, (struct watched_entry) {
+ .kind = WATCH_KIND_FILE,
+ .file_info = {
+ .absolutepath = real_path,
+ .directory = file_directory(real_path),
+ .filename = file_name(real_path)
+ }
+ });
+
+ return true;
}
bool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback)
{
- if ((hotloader->enabled) ||
- (!hotloader->watch_count)) {
- return false;
- }
+ if (hotloader->enabled || !hotloader->watch_count) return false;
CFStringRef string_refs[hotloader->watch_count];
for (unsigned index = 0; index < hotloader->watch_count; ++index) {
+ struct watched_entry *watch_info = hotloader->watch_list + index;
+ char *directory = watch_info->kind == WATCH_KIND_FILE
+ ? watch_info->file_info.directory
+ : watch_info->catalog_info.directory;
string_refs[index] = CFStringCreateWithCString(kCFAllocatorDefault,
- hotloader->watch_list[index].directory,
+ directory,
kCFStringEncodingUTF8);
}
- FSEventStreamContext context = {};
- context.info = (void *) hotloader;
+ FSEventStreamContext context = {
+ .info = hotloader
+ };
+
+ hotloader->path = (CFArrayRef) CFArrayCreate(NULL,
+ (const void **) string_refs,
+ hotloader->watch_count,
+ &kCFTypeArrayCallBacks);
+
+ hotloader->flags = kFSEventStreamCreateFlagNoDefer |
+ kFSEventStreamCreateFlagFileEvents;
- hotloader->enabled = true;
- hotloader->callback = callback;
- hotloader->path = (CFArrayRef) CFArrayCreate(NULL, (const void **) string_refs, hotloader->watch_count, &kCFTypeArrayCallBacks);
- hotloader->flags = kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents;
hotloader->stream = FSEventStreamCreate(NULL,
hotloader_handler,
&context,
@@ -153,27 +273,43 @@ bool hotloader_begin(struct hotloader *hotloader, hotloader_callback *callback)
kFSEventStreamEventIdSinceNow,
0.5,
hotloader->flags);
- FSEventStreamScheduleWithRunLoop(hotloader->stream, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
+
+ FSEventStreamScheduleWithRunLoop(hotloader->stream,
+ CFRunLoopGetMain(),
+ kCFRunLoopDefaultMode);
+
+ hotloader->callback = callback;
FSEventStreamStart(hotloader->stream);
+ hotloader->enabled = true;
+
return true;
}
void hotloader_end(struct hotloader *hotloader)
{
- if (hotloader->enabled) {
- FSEventStreamStop(hotloader->stream);
- FSEventStreamInvalidate(hotloader->stream);
- FSEventStreamRelease(hotloader->stream);
-
- CFIndex count = CFArrayGetCount(hotloader->path);
- for (unsigned index = 0; index < count; ++index) {
- CFStringRef string_ref = (CFStringRef) CFArrayGetValueAtIndex(hotloader->path, index);
- free(hotloader->watch_list[index].directory);
- free(hotloader->watch_list[index].filename);
- CFRelease(string_ref);
- }
+ if (!hotloader->enabled) return;
+
+ FSEventStreamStop(hotloader->stream);
+ FSEventStreamInvalidate(hotloader->stream);
+ FSEventStreamRelease(hotloader->stream);
- CFRelease(hotloader->path);
- memset(hotloader, 0, sizeof(struct hotloader));
+ CFIndex count = CFArrayGetCount(hotloader->path);
+ for (unsigned index = 0; index < count; ++index) {
+ struct watched_entry *watch_info = hotloader->watch_list + index;
+ if (watch_info->kind == WATCH_KIND_FILE) {
+ free(watch_info->file_info.absolutepath);
+ free(watch_info->file_info.directory);
+ free(watch_info->file_info.filename);
+ } else if (watch_info->kind == WATCH_KIND_CATALOG) {
+ free(watch_info->catalog_info.directory);
+ if (watch_info->catalog_info.extension) {
+ free(watch_info->catalog_info.extension);
+ }
+ }
+ CFRelease(CFArrayGetValueAtIndex(hotloader->path, index));
}
+
+ CFRelease(hotloader->path);
+ free(hotloader->watch_list);
+ memset(hotloader, 0, sizeof(struct hotloader));
}