├── hierarchy.h ├── win32_loop_edit.h ├── win32_live_edit.h ├── readme.md └── live_variable.h /hierarchy.h: -------------------------------------------------------------------------------- 1 | // TODO: 2 | // - hash the names for faster comparison 3 | // - add parent node separately from name 4 | 5 | #ifndef DEBUG_HIERARCHY_H 6 | 7 | // types: 8 | // - live editable variables - local & config for values 9 | // - profiling - added in functions 10 | // - variable inspection/observation - local only 11 | // - ...? 12 | 13 | #ifdef DEBUG_HIERARCHY_MAX_LENGTH 14 | #define DEBUG_HIERARCHY_COUNTER DEBUG_HIERARCHY_MAX_LENGTH 15 | #else 16 | #define DEBUG_HIERARCHY_COUNTER __COUNTER__ 17 | #endif 18 | #ifndef DEBUG_ATOMIC_EXCHANGE /* user can define it to e.g. C11 atomic_exchange or Windows InterlockedExchange */ 19 | /* sets bool to 1, evaluates to previous value of bool */ 20 | #define DEBUG_ATOMIC_EXCHANGE(ptr, desired) (*(ptr) ? 1 : (*(ptr))++) 21 | #endif 22 | #ifndef DEBUG_HIERARCHY_MULTIPLE 23 | #define DEBUG_HIERARCHY_MULTIPLE 3 24 | #endif//DEBUG_HIERARCHY_MULTIPLE 25 | 26 | typedef struct debug_hierarchy_el 27 | { 28 | enum { 29 | DebugHierarchyGroup, 30 | #define DEBUG_HIERARCHY_KIND(type, kind) DebugHierarchyKind_## kind, 31 | DEBUG_HIERARCHY_KINDS 32 | #undef DEBUG_HIERARCHY_KIND 33 | } Kind; // reserve 0 for groups 34 | unsigned int NameLength; 35 | char *Name; 36 | struct debug_hierarchy_el *Parent; 37 | struct debug_hierarchy_el *FirstChild; 38 | struct debug_hierarchy_el *NextSibling; 39 | void *Data; 40 | int IsClosed; 41 | } debug_hierarchy_el; 42 | 43 | 44 | debug_hierarchy_el DebugHierarchy[]; 45 | static unsigned int DebugHierarchyCount = 0; 46 | unsigned int DebugHierarchy_ArrayLen; 47 | 48 | static int 49 | DebugHierarchy_Level(debug_hierarchy_el El) 50 | { 51 | int Result = 0; 52 | for(; El.Parent; El = *El.Parent, ++Result); 53 | return Result; 54 | } 55 | 56 | static int 57 | DebugHierarchy_Equal(debug_hierarchy_el El1, debug_hierarchy_el El2) 58 | { 59 | int Result = 0; 60 | if(El1.NameLength == El2.NameLength) 61 | { 62 | Result = 1; 63 | unsigned int At = El1.NameLength; 64 | while(At--) 65 | { 66 | if(El1.Name[At] != El2.Name[At]) 67 | { Result = 0; } 68 | } 69 | } 70 | return Result; 71 | } 72 | 73 | static debug_hierarchy_el * 74 | DebugHierarchy_Next(debug_hierarchy_el *Item) 75 | { 76 | debug_hierarchy_el *Result = 0; 77 | if(Item->FirstChild && ! Item->IsClosed) { Result = Item->FirstChild; } 78 | else if(Item->NextSibling) { Result = Item->NextSibling; } 79 | else while(Item->Parent) 80 | { 81 | if(Item->Parent->NextSibling) { Result = Item->Parent->NextSibling; break; } 82 | else { Item = Item->Parent; } 83 | } 84 | return Result; 85 | } 86 | 87 | static void 88 | DebugHierarchy_AppendChild(debug_hierarchy_el *Parent, debug_hierarchy_el *Appendee) 89 | { 90 | Assert(Appendee); 91 | Assert(Parent); 92 | Appendee->Parent = Parent; 93 | debug_hierarchy_el *Child = Parent->FirstChild; 94 | if(Child) 95 | { 96 | int AppendeeAlreadyInHierarchy = DebugHierarchy_Equal(*Child, *Appendee); 97 | debug_hierarchy_el *NextChild = Child->NextSibling; 98 | while(NextChild && ! AppendeeAlreadyInHierarchy) 99 | { 100 | Child = NextChild; 101 | NextChild = Child->NextSibling; 102 | AppendeeAlreadyInHierarchy = DebugHierarchy_Equal(*Child, *Appendee); 103 | } 104 | if(AppendeeAlreadyInHierarchy) { return; } 105 | Child->NextSibling = Appendee; 106 | } 107 | else 108 | { 109 | Parent->FirstChild = Appendee; 110 | } 111 | } 112 | 113 | // returns 1 if new element was added, 0 if found an existing element 114 | // puts pointer to element added (or existing equivalent element) in Location 115 | static int 116 | DebugHierarchy_AddUniqueEl(debug_hierarchy_el *Hierarchy, debug_hierarchy_el *DefaultParent, 117 | unsigned int *Count, unsigned int MaxLen, 118 | debug_hierarchy_el El, debug_hierarchy_el **Location) 119 | { 120 | int AlreadyAdded = 0; 121 | if(! El.Parent) 122 | { El.Parent = DefaultParent; } 123 | for(unsigned int i = 0, N = *Count; i <= N; ++i) 124 | { 125 | if(DebugHierarchy_Equal(El, Hierarchy[i])) 126 | { 127 | AlreadyAdded = 1; 128 | *Location = Hierarchy + i; 129 | break; 130 | } 131 | } 132 | 133 | if(! AlreadyAdded) 134 | { 135 | unsigned int iInsert = ++*Count; 136 | if(iInsert < MaxLen) 137 | { 138 | Hierarchy[iInsert] = El; 139 | *Location = Hierarchy + iInsert; 140 | } 141 | else 142 | { 143 | // TODO: handle error -> make the last element say that the array is full? 144 | } 145 | } 146 | Assert(*Location); 147 | int Result = ! AlreadyAdded; 148 | return Result; 149 | } 150 | 151 | static debug_hierarchy_el * 152 | DebugHierarchy_InitElement(char *Name, void *Data, int Kind, debug_hierarchy_el *Parent) 153 | { 154 | if(! Parent) { Parent = DebugHierarchy; } 155 | char *LevelName = Name; 156 | debug_hierarchy_el Group = {0}; 157 | for(char *At = Name; *At; ++At) 158 | { 159 | if(*At == '_' && Group.NameLength && At[1]) // not the end of the string or repeated '_' 160 | { 161 | Group.Name = LevelName; 162 | if(DebugHierarchy_AddUniqueEl(DebugHierarchy, Parent, &DebugHierarchyCount, 163 | DebugHierarchy_ArrayLen, Group, &Group.Parent)) 164 | { DebugHierarchy_AppendChild(Group.Parent->Parent, Group.Parent); } 165 | 166 | while(*++At == '_'); 167 | LevelName = At; 168 | Group.NameLength = 1; 169 | } 170 | else 171 | { 172 | ++Group.NameLength; 173 | } 174 | } 175 | 176 | Group.Data = Data; 177 | Group.Kind = Kind; 178 | Group.Name = LevelName; 179 | debug_hierarchy_el *Result = 0; 180 | DebugHierarchy_AddUniqueEl(DebugHierarchy, Parent, &DebugHierarchyCount, 181 | DebugHierarchy_ArrayLen, Group, &Result); 182 | DebugHierarchy_AppendChild(Result->Parent, Result); 183 | 184 | Assert(Result->Parent != Result->NextSibling); 185 | return Result; 186 | } 187 | #define DEBUG_HIERARCHY_INIT_EL(name, data, kind) \ 188 | static int DebugHierarchy_## name ##_IsInit; \ 189 | if(! DEBUG_ATOMIC_EXCHANGE(DebugHierarchy_## name ##_IsInit)) \ 190 | { DebugHierarchy_InitElement(#name, data, kind, 0); DEBUG_HIERARCHY_COUNTER; } 191 | 192 | #define DEBUG_HIERARCHY_DECLARATION \ 193 | enum { DebugHierarchy_ArrayLenEnum = DEBUG_HIERARCHY_MULTIPLE * DEBUG_HIERARCHY_COUNTER + 1}; \ 194 | debug_hierarchy_el DebugHierarchy[DebugHierarchy_ArrayLenEnum]; \ 195 | unsigned int DebugHierarchy_ArrayLen = DebugHierarchy_ArrayLenEnum; 196 | 197 | #define DEBUG_HIERARCHY_H 198 | #endif//DEBUG_HIERARCHY_H 199 | -------------------------------------------------------------------------------- /win32_loop_edit.h: -------------------------------------------------------------------------------- 1 | #ifndef LOOP_EDIT_H 2 | 3 | #define WIN32_STATE_FILE_NAME_COUNT MAX_PATH 4 | typedef struct win32_replay_buffer 5 | { 6 | // TODO: use FILE? 7 | HANDLE FileHandle; 8 | HANDLE MemoryMap; 9 | char Filename[WIN32_STATE_FILE_NAME_COUNT]; 10 | void *MemoryBlock; 11 | } win32_replay_buffer; 12 | 13 | typedef struct win32_loop_info 14 | { 15 | win32_replay_buffer ReplayBuffers[4]; 16 | 17 | HANDLE RecordingHandle; 18 | int InputRecordingIndex; 19 | 20 | HANDLE PlaybackHandle; 21 | int InputPlayingIndex; 22 | 23 | // TODO: should I move this out of the struct? It's somewhat separate... 24 | exe_info EXE; 25 | } win32_loop_info; 26 | 27 | // TODO: allow looping input without recording/resetting state 28 | internal void 29 | Win32GetInputFileLocation(win32_loop_info *LoopInfo, b32 InputStream, int SlotIndex, int DestCount, char *Dest) 30 | { 31 | char Temp[64]; 32 | wsprintf(Temp, "loop_%d_%s.mem", SlotIndex, InputStream ? "input" : "state"); 33 | Win32BuildPathFilename(LoopInfo->EXE.PathName, LoopInfo->EXE.Filename_FromPath, 34 | Temp, DestCount, Dest); 35 | } 36 | 37 | internal win32_replay_buffer * 38 | Win32GetReplayBuffer(win32_loop_info *LoopInfo, int unsigned Index) 39 | { 40 | Assert(Index > 0); 41 | Assert(Index < ArrayCount(LoopInfo->ReplayBuffers)); 42 | win32_replay_buffer *Result = &LoopInfo->ReplayBuffers[Index]; 43 | return Result; 44 | } 45 | 46 | internal void 47 | Win32BeginRecordingInput(win32_loop_info *LoopInfo, int InputRecordingIndex, program_memory Memory) 48 | { 49 | win32_replay_buffer *ReplayBuffer = Win32GetReplayBuffer(LoopInfo, InputRecordingIndex); 50 | if(ReplayBuffer->MemoryBlock) 51 | { 52 | LoopInfo->InputRecordingIndex = InputRecordingIndex; 53 | 54 | char Filename[WIN32_STATE_FILE_NAME_COUNT]; 55 | Win32GetInputFileLocation(LoopInfo, 1, InputRecordingIndex, sizeof(Filename), Filename); 56 | LoopInfo->RecordingHandle = CreateFileA(Filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); 57 | 58 | 59 | #if 0 60 | LARGE_INTEGER FilePosition; 61 | FilePosition.QuadPart = LoopInfo->TotalSize; 62 | SetFilePointerEx(LoopInfo->RecordingHandle, FilePosition, 0, FILE_BEGIN); 63 | #endif 64 | 65 | CopyMemory(ReplayBuffer->MemoryBlock, Memory.MemoryBlock, Memory.TotalSize); 66 | } 67 | } 68 | 69 | internal void 70 | Win32EndRecordingInput(win32_loop_info *LoopInfo) 71 | { 72 | CloseHandle(LoopInfo->RecordingHandle); 73 | LoopInfo->InputRecordingIndex = 0; 74 | } 75 | 76 | internal void 77 | Win32BeginInputPlayback(win32_loop_info *LoopInfo, int InputPlayingIndex, program_memory Memory) 78 | { 79 | win32_replay_buffer *ReplayBuffer = Win32GetReplayBuffer(LoopInfo, InputPlayingIndex); 80 | if(ReplayBuffer->MemoryBlock) 81 | { 82 | LoopInfo->InputPlayingIndex = InputPlayingIndex; 83 | 84 | char Filename[WIN32_STATE_FILE_NAME_COUNT]; 85 | Win32GetInputFileLocation(LoopInfo, 1, InputPlayingIndex, sizeof(Filename), Filename); 86 | LoopInfo->PlaybackHandle = CreateFileA(Filename, GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); 87 | 88 | #if 0 89 | LARGE_INTEGER FilePosition; 90 | FilePosition.QuadPart = LoopInfo->TotalSize; 91 | SetFilePointerEx(LoopInfo->PlaybackHandle, FilePosition, 0, FILE_BEGIN); 92 | #endif 93 | 94 | CopyMemory(Memory.MemoryBlock, ReplayBuffer->MemoryBlock, Memory.TotalSize); 95 | } 96 | } 97 | 98 | internal void 99 | Win32EndInputPlayback(win32_loop_info *LoopInfo) 100 | { 101 | CloseHandle(LoopInfo->RecordingHandle); 102 | LoopInfo->InputPlayingIndex = 0; 103 | } 104 | 105 | internal void 106 | Win32RecordInput(win32_loop_info *LoopInfo, input *NewInput) 107 | { 108 | DWORD BytesWritten; 109 | WriteFile(LoopInfo->RecordingHandle, NewInput, sizeof(*NewInput), &BytesWritten, 0); 110 | } 111 | 112 | internal void 113 | Win32PlayBackInput(win32_loop_info *LoopInfo, input *NewInput, program_memory Memory) 114 | { 115 | DWORD BytesRead = 0; 116 | if(ReadFile(LoopInfo->PlaybackHandle, NewInput, sizeof(*NewInput), &BytesRead, 0)) 117 | { 118 | if(BytesRead == 0) 119 | { 120 | // NOTE: We've hit the end of the stream, go back to the beginning 121 | int PlayingIndex = LoopInfo->InputPlayingIndex; 122 | Win32EndInputPlayback(LoopInfo); 123 | Win32BeginInputPlayback(LoopInfo, PlayingIndex, Memory); 124 | ReadFile(LoopInfo->PlaybackHandle, NewInput, sizeof(*NewInput), &BytesRead, 0); 125 | } 126 | } 127 | } 128 | 129 | internal void 130 | Win32SingleInputLoopControl(win32_loop_info *LoopInfo, int SlotIndex, program_memory Memory) 131 | { 132 | if(LoopInfo->InputPlayingIndex == 0) 133 | { 134 | if(LoopInfo->InputRecordingIndex == 0) 135 | { 136 | Win32BeginRecordingInput(LoopInfo, SlotIndex, Memory); 137 | } 138 | else 139 | { 140 | Win32EndRecordingInput(LoopInfo); 141 | Win32BeginInputPlayback(LoopInfo, SlotIndex, Memory); 142 | } 143 | } 144 | else 145 | { 146 | Win32EndInputPlayback(LoopInfo); 147 | } 148 | } 149 | 150 | // TODO: allow any input struct (void *Input, size_t sizeofInput) 151 | internal void 152 | Win32RecordOrPlayBackInput(win32_loop_info *LoopInfo, input *Input, program_memory Memory) 153 | { 154 | if(LoopInfo->InputRecordingIndex) 155 | { 156 | Win32RecordInput(LoopInfo, Input); 157 | } 158 | // TODO: check HMH... should be else if? 159 | if(LoopInfo->InputPlayingIndex) 160 | { 161 | Win32PlayBackInput(LoopInfo, Input, Memory); 162 | } 163 | } 164 | 165 | internal void 166 | Win32InitReplayBuffers(win32_loop_info *LoopInfo, u64 MemorySize) 167 | { 168 | for(int ReplayIndex = 1; 169 | ReplayIndex < ArrayCount(LoopInfo->ReplayBuffers); 170 | ++ReplayIndex) 171 | { 172 | win32_replay_buffer *ReplayBuffer = &LoopInfo->ReplayBuffers[ReplayIndex]; 173 | // TODO: Recording system still seems to take too long 174 | // on record start - find out what Windows is doing and if 175 | // we can speed up / defer some of that processing. 176 | 177 | Win32GetInputFileLocation(LoopInfo, 0, ReplayIndex, 178 | sizeof(ReplayBuffer->Filename), ReplayBuffer->Filename); 179 | 180 | ReplayBuffer->FileHandle = 181 | CreateFileA(ReplayBuffer->Filename, 182 | GENERIC_WRITE|GENERIC_READ, 0, 0, CREATE_ALWAYS, 0, 0); 183 | 184 | LARGE_INTEGER MaxSize; 185 | MaxSize.QuadPart = MemorySize; 186 | ReplayBuffer->MemoryMap = CreateFileMapping( 187 | ReplayBuffer->FileHandle, 0, PAGE_READWRITE, 188 | MaxSize.HighPart, MaxSize.LowPart, 0); 189 | 190 | ReplayBuffer->MemoryBlock = MapViewOfFile( 191 | ReplayBuffer->MemoryMap, FILE_MAP_ALL_ACCESS, 0, 0, MemorySize); 192 | 193 | if(ReplayBuffer->MemoryBlock) 194 | { 195 | 196 | } 197 | else 198 | { 199 | // TODO: Diagnostic 200 | } 201 | } 202 | } 203 | 204 | internal win32_loop_info 205 | Win32InitLoop(size_t MemorySize) 206 | { 207 | win32_loop_info LoopInfo = {0}; 208 | Win32GetEXEFilename(LoopInfo.EXE.PathName, sizeof(LoopInfo.EXE.PathName), &LoopInfo.EXE.Filename_FromPath); 209 | Win32InitReplayBuffers(&LoopInfo, MemorySize); 210 | 211 | return LoopInfo; 212 | } 213 | #define LOOP_EDIT_H 214 | #endif 215 | -------------------------------------------------------------------------------- /win32_live_edit.h: -------------------------------------------------------------------------------- 1 | /* NOTE: 2 | * - path => C:\Documents\my_file.txt 3 | * - dir => C:\Documents\ 4 | * - name => myfile.txt 5 | */ 6 | 7 | #ifndef WIN32_LIVE_EDIT_H 8 | #include 9 | 10 | typedef void VoidFn(); 11 | 12 | typedef struct LiveEditLibSym 13 | { 14 | char *name; 15 | union { 16 | VoidFn *function; 17 | void *data; 18 | }; // symbol; 19 | } LiveEditLibSym; 20 | 21 | typedef enum LiveEditPathType { 22 | LIVE_EDIT_REL_PATH, 23 | LIVE_EDIT_ABS_PATH, 24 | } LiveEditPathType; 25 | 26 | typedef enum LiveEditCopy { 27 | LIVE_EDIT_COPY_NONE, 28 | LIVE_EDIT_COPY_LOCAL, 29 | LIVE_EDIT_COPY_TEMP_DIR, 30 | } LiveEditCopy; 31 | 32 | // NOTE: Never use MAX_PATH in code that is user-facing, because it 33 | // can be dangerous and lead to bad results. 34 | #define LIVE_EDIT_MAX_PATH MAX_PATH 35 | 36 | typedef struct LiveEditLib 37 | { 38 | HMODULE dll; 39 | FILETIME dll_last_write_time; 40 | 41 | // IMPORTANT: callbacks can be 0! Check before calling. 42 | unsigned int syms_n; 43 | LiveEditLibSym *syms; 44 | 45 | struct { 46 | char dll[LIVE_EDIT_MAX_PATH]; 47 | char lock[LIVE_EDIT_MAX_PATH]; 48 | int dll_dir_len; 49 | } paths; 50 | 51 | int has_symbols; 52 | } LiveEditLib; 53 | 54 | // concatenates dir with name to make path 55 | static void 56 | Win32PathFromDirName(char *dir_str, size_t dir_str_len, char *name_str, char *path_buf, size_t path_buf_len) 57 | { 58 | size_t Count = 0; 59 | for(size_t i = 0; i < dir_str_len && Count++ <= path_buf_len; ++i) 60 | { *path_buf++ = *dir_str++; } 61 | 62 | while(*name_str && Count++ <= path_buf_len) 63 | { *path_buf++ = *name_str++; } 64 | 65 | assert(Count < path_buf_len); 66 | *path_buf = 0; 67 | } 68 | 69 | static inline FILETIME 70 | le__Get_last_write_time(char *Filename) 71 | { 72 | FILETIME LastWriteTime = {0}; 73 | 74 | WIN32_FILE_ATTRIBUTE_DATA Data; 75 | if (GetFileAttributesExA(Filename, GetFileExInfoStandard, &Data)) 76 | { LastWriteTime = Data.ftLastWriteTime; } 77 | 78 | return LastWriteTime; 79 | } 80 | 81 | static inline int 82 | le__file_is_present(char *file_path) 83 | { 84 | WIN32_FILE_ATTRIBUTE_DATA Ignored; 85 | return file_path && GetFileAttributesExA(file_path, GetFileExInfoStandard, &Ignored); 86 | } 87 | 88 | /// 1 for success, 0 for failure 89 | static int 90 | Win32LoadLib(LiveEditLib *lib, LiveEditCopy copy_kind) 91 | { 92 | if (! le__file_is_present(lib->paths.lock)) // don't try and load while the lock file is there 93 | { 94 | char *dll_path = lib->paths.dll; 95 | char tmp_dll_buf[MAX_PATH + 1]; 96 | 97 | lib->dll_last_write_time = le__Get_last_write_time(lib->paths.dll); 98 | 99 | if (copy_kind) 100 | { 101 | char tmp_dir[MAX_PATH + 1]; 102 | 103 | switch (copy_kind) 104 | { 105 | case LIVE_EDIT_COPY_TEMP_DIR: GetTempPathA(sizeof(tmp_dir), tmp_dir); break; 106 | case LIVE_EDIT_COPY_LOCAL: { 107 | memcpy(tmp_dir, lib->paths.dll, lib->paths.dll_dir_len); 108 | tmp_dir[lib->paths.dll_dir_len] = '\0'; 109 | } break; 110 | 111 | default: fprintf(stderr, "Unknown copy kind: %d\n", copy_kind); assert(0); 112 | } 113 | 114 | // TODO: use CreateFile("%TEMP\\...", ... FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | ...) 115 | UINT tmp_ok = GetTempFileNameA(tmp_dir, "wbt", 0, tmp_dll_buf); 116 | assert(tmp_ok); 117 | 118 | if (! CopyFileA(dll_path, tmp_dll_buf, 0)) 119 | { 120 | int err = GetLastError(); 121 | assert(!"copy failed"); 122 | err++; 123 | } 124 | 125 | dll_path = tmp_dll_buf; 126 | } 127 | 128 | lib->dll = LoadLibraryA(dll_path); 129 | // NOTE: if this fails, you may be trying to load an invalid path 130 | if (! lib->dll) { 131 | int err = GetLastError(); (void)err; 132 | assert(0); 133 | } 134 | 135 | lib->has_symbols = 1; 136 | for(unsigned int sym_i = 0; sym_i < lib->syms_n; ++sym_i) 137 | { 138 | lib->syms[sym_i].function = (VoidFn *)GetProcAddress(lib->dll, lib->syms[sym_i].name); 139 | 140 | // NOTE: if this fails, check you are exporting the function from the dll 141 | assert(lib->syms[sym_i].function); //{ lib->has_symbols = 0; } 142 | } 143 | } 144 | 145 | assert(lib->has_symbols); 146 | return !! lib->has_symbols; 147 | } 148 | 149 | static void 150 | Win32UnloadLib(LiveEditLib *lib) 151 | { 152 | if (lib->dll) 153 | { FreeLibrary(lib->dll); } 154 | 155 | for(unsigned int sym_i = 0; sym_i < lib->syms_n; ++sym_i) 156 | { lib->syms[sym_i].function = 0; } 157 | 158 | lib->has_symbols = 0; 159 | lib->dll = 0; 160 | } 161 | 162 | static int 163 | Win32ReloadLibOnRecompile(LiveEditLib *lib, LiveEditCopy copy_kind) 164 | { 165 | int LibLoaded = 0; 166 | FILETIME new_dll_write_time = le__Get_last_write_time(lib->paths.dll); 167 | int dll_has_been_changed = CompareFileTime(&new_dll_write_time, &lib->dll_last_write_time) != 0; 168 | int lock_has_been_removed = ! le__file_is_present(lib->paths.lock); 169 | // See if file has been changed and the lockfile has been removed 170 | if (dll_has_been_changed && lock_has_been_removed) 171 | { 172 | Win32UnloadLib(lib); 173 | LibLoaded = Win32LoadLib(lib, copy_kind); 174 | } 175 | return LibLoaded; 176 | } 177 | 178 | static void le__strcpy(char *Dest, char *Src) { while(*Src) { *Dest++ = *Src++; } } 179 | 180 | // TODO: fail (or provide some feedback) if dll doesn't exist 181 | static LiveEditLib 182 | Win32Library(LiveEditLibSym *syms, unsigned int syms_n, LiveEditPathType path_type, 183 | char *dll_path, char *lock_path) 184 | { 185 | LiveEditLib lib = {0}; 186 | lib.syms_n = syms_n; 187 | lib.syms = syms; 188 | 189 | assert(dll_path); 190 | if (path_type == LIVE_EDIT_ABS_PATH) 191 | { 192 | le__strcpy(lib.paths.dll, dll_path); 193 | if (lock_path) 194 | { le__strcpy(lib.paths.lock, lock_path); } 195 | } 196 | 197 | else 198 | { // construct utility paths from names and exe path 199 | char exe_path[LIVE_EDIT_MAX_PATH]; 200 | char *exe_name_from_path = exe_path; // Points to the char after the last \ in exe_path 201 | unsigned int path_str_len = 0; 202 | { // Get name and path for currently running exe 203 | path_str_len = GetModuleFileNameA(0, exe_path, sizeof(exe_path)); 204 | assert(path_str_len); 205 | if (path_str_len == sizeof(exe_path)) // maybe perfectly filled, or maybe truncated 206 | { assert(GetLastError() != ERROR_INSUFFICIENT_BUFFER); } 207 | 208 | for(char *at = exe_path; *at; ++at) 209 | { 210 | if (*at == '\\') 211 | { exe_name_from_path = at + 1; } // this may be '\0' (an empty string), which is intentional 212 | } 213 | } 214 | lib.paths.dll_dir_len = exe_name_from_path - exe_path; 215 | 216 | Win32PathFromDirName(exe_path, lib.paths.dll_dir_len, dll_path, lib.paths.dll, sizeof(lib.paths.dll)); 217 | if (lock_path) 218 | { Win32PathFromDirName(exe_path, lib.paths.dll_dir_len, lock_path, lib.paths.lock, sizeof(lib.paths.lock)); } 219 | } 220 | 221 | assert(le__file_is_present(lib.paths.dll)); 222 | 223 | return lib; 224 | } 225 | 226 | #define WIN32_LIVE_EDIT_H 227 | #endif//WIN32_LIVE_EDIT_H 228 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Live Edit 2 | _A collection of single-header libraries to help with reloading, debugging and profiling C(++) code._ 3 | 4 | ## Contents 5 | - [Acknowledgments](#acknowledgments) 6 | - [Overview of headers](#overview-of-headers) 7 | - [win32_live_edit.h](#win32_live_edith) 8 | - [win32_loop_edit.h](#win32_loop_edith) 9 | - [live_variable.h](#live_variableh) 10 | - [DEBUG_WATCH](#debug_watch) 11 | - [DEBUG_TWEAK](#debug_tweak) 12 | - [hierarchy.h](#hierarchyh) 13 | - [Recommended libraries](#recommended-libraries) 14 | 15 | ## Acknowledgments 16 | Most of the content here used Casey Muratori's [Handmade Hero](https://www.handmadehero.org) as a starting point. 17 | It is an invaluable resource for those looking to understand and write low-level code. 18 | Go and watch a [few episodes](https://hero.handmade.network/episode/code) (lovingly annotated by Miblo), 19 | maybe chip in a few quid/dollars, then come back here. I'll wait. 20 | 21 | I've put some relevant links in each section. 22 | My implementations have diverged somewhat, but the concepts are mostly the same. 23 | 24 | One major difference is that these will work with C. 25 | (They're not all C89-compatible, as some of the implementations require declare-anywhere and/or `__VA_ARGS__`. 26 | Some aren't C89-compatible just because I haven't taken the time to make them so. 27 | File an issue or a pull request if you want this changed. Ditto for C++ incompatibilities.) 28 | 29 | ## Overview of headers 30 | All of these can be used standalone, but they also work well together. 31 | - `win32_live_edit.h` - reloading DLL code while running (requires a platform and client layer, see video linked above). Currently Windows only. 32 | - `win32_loop_edit.h` - (needs updating) - capture input for a set period of time, reset the system state to the start, and loop over, e.g. for tweaking small changes in timing-sensitive code. Currently Windows only. 33 | - `live_variable.h` - introducing `DEBUG_WATCH()` and `DEBUG_TWEAK()` for watching and tweaking variables (surprisingly!) from the application. Also `DEBUG_LIVE_IF()`, useful for toggling code, similar to `#if 0` blocks. 34 | - `hierarchy.h` - used for sorting arbitrary debug data into a hierarchy/tree structure. 35 | *TODO: add`profiling.h`* 36 | 37 | Note: if you use these, please keep a copy and don't pull updates blindly. Assume every update may contain breaking API changes. 38 | 39 | ## `win32_live_edit.h` 40 | Casey's version: 41 | [Day 021: Loading Game Code Dynamically](https://hero.handmade.network/episode/code/day021/); 42 | [Day 022: Instantaneous Live Code Editing](https://hero.handmade.network/episode/code/day022/) 43 | 44 | ``` c 45 | // "main.h" -> #included in both the following files 46 | typedef void update_and_render(image_buffer Buffer, memory *Memory); 47 | /***/ 48 | 49 | // "win32_main.c" 50 | #include "path/to/win32_live_edit.h" 51 | 52 | // Once, on startup: 53 | #if !SINGLE_EXECUTABLE // not needed, but used in a few other places as well, 54 | // you can swap between live editing internally and a 55 | // single executable for release by changing one number 56 | char *LibFnNames[] = { "UpdateAndRender" }; 57 | win32_library Lib = Win32Library(LibFnNames, ArrayCount(LibFnNames), 58 | 0, "main.dll", "main_temp.dll", "lock.tmp"); 59 | #endif // !SINGLE_EXECUTABLE 60 | 61 | // later, once every so often (e.g. once per frame) 62 | #if !SINGLE_EXECUTABLE 63 | Win32ReloadLibOnRecompile(&Lib); 64 | update_and_render *UpdateAndRender = ((update_and_render *)Lib.Functions[0].Function); 65 | // Not much point in continuing if key function failed to load: 66 | Assert(UpdateAndRender && "loaded library function."); 67 | #endif // !SINGLE_EXECUTABLE 68 | /***/ 69 | 70 | // "main.c" -> compiled/linked as DLL 71 | void UpdateAndRender(image_buffer Buffer, memory *Memory) { 72 | // Use Memory rather than global variables, as any pointers to them will be invalidated on recompile (roughly) 73 | if( ! Memory->IsInitialized) { 74 | // init stuff on first runthrough 75 | Memory->IsInitialized = 1; 76 | } 77 | // ... 78 | } 79 | ``` 80 | 81 | ## `win32_loop_edit.h` 82 | Casey's Version: 83 | [Day 023: Looped Live Code Editing](https://hero.handmade.network/episode/code/day023/) 84 | 85 | *TODO: ensure working & explain`* 86 | 87 | ## `live_variable.h` 88 | Casey experiments with multiple implementations doing something vaguely similar to this between 89 | [Day 192: Implementing Self-Recompilation](https://hero.handmade.network/episode/code/day192/) and 90 | [Day 255: Building a Profile Tree](https://hero.handmade.network/episode/code/day255/). 91 | I think most of the stuff similar to this is in the 212-214 range. 92 | 93 | ### `DEBUG_WATCH` 94 | WATCH is there to minimize the friction involved with inspecting the state of the system. 95 | ``` c 96 | // "main.c" 97 | #define DEBUG_TYPES \ 98 | DEBUG_TYPE(f32, "%ff") \ 99 | DEBUG_TYPE(v2, "{ %ff, %ff }", DEBUG_MEMBER(v2, X), DEBUG_MEMBER(v2, Y)) 100 | 101 | // Only need to define this if the values being watched/tweaked are declared in many threads 102 | // as far as I'm aware, most platforms have an atomic exchange function. This is from C11: 103 | #define DEBUG_ATOMIC_EXCHANGE(ValuePtr, Desired) atomic_exchange(ValuePtr, Desired) 104 | #include "path/to/live_variable.h" // as long as the types are defined first 105 | // ... 106 | 107 | DEBUG_WATCH(f32, X) = 6.2f; // previously: `f32 X = 6.2f;` 108 | // Tag the variable with a path without affecting its local name (mostly for interaction with `hierarchy.h`): 109 | DEBUG_WATCHED(v2, Category_Name, Vec) = { 1.f, 0.f }; 110 | // ... 111 | 112 | for(unsigned int i = 1; i <= DebugWatchCount; ++i) { 113 | debug_variable Var = DebugWatchVariables[i]; 114 | switch(Var->Type) { // you can do fancy things with the DEBUG_TYPES macro, but let's K.I.S.S. here 115 | case DebugVarType_f32: printf("%f\n", *(f32 *)Var.Data); break; 116 | 117 | case DebugVarType_v2: { 118 | v2 V = *(v2 *)Var.Data; 119 | printf("%f, %f\n", V.X, V.Y); 120 | } break; 121 | } 122 | } 123 | 124 | // ... after the final function that uses DEBUG_WATCH. (In the global space) 125 | DEBUG_WATCH_DECLARATION 126 | ``` 127 | 128 | ### `DEBUG_TWEAK` 129 | TWEAK is a bit more involved than WATCH, but you get a bit more from it: you can edit values and save them between compiles. This is primarily for toggling code sections and tweaking values. 130 | ``` c 131 | // "main.c" 132 | #define DEBUG_TYPES \ 133 | DEBUG_TYPE(bool32, "%d") \ 134 | DEBUG_TYPE(f32, "%ff") \ 135 | DEBUG_TYPE_STRUCT(v2, "{ %ff, %ff }", DEBUG_MEMBER(v2, X), DEBUG_MEMBER(v2, Y)) 136 | 137 | // (see above) 138 | #define DEBUG_ATOMIC_EXCHANGE(ValuePtr, Desired) atomic_exchange(ValuePtr, Desired) 139 | #include "main_live.h" 140 | /***/ 141 | 142 | // "main_live.h" -> this file should be overwritten when a value changes (as below), 143 | // using the format specifiers above 144 | #define DEBUG_LIVE_VARS \ 145 | DEBUG_LIVE_IF(RenderBadly, 0) \ 146 | DEBUG_LIVE_TWEAK(f32, TestFloat, 999.000000f) \ 147 | DEBUG_LIVE_TWEAK(v2, TestV2, { 2381.000000f, 303.450000f }) \ 148 | /* important empty space here */ 149 | #include "path/to/live_variable.h" 150 | /***/ 151 | 152 | // "main.c" 153 | // important type definitions... 154 | #define DEBUG_TWEAK_IMPLEMENTATION 155 | #include "path/to/live_variable.h" 156 | // ... 157 | 158 | DEBUG_LIVE_if(RenderBadly) { // ... lots of things } // use just like normal if statement 159 | else { RenderWell(); } 160 | printf("%f\n", TestFloat); 161 | v2 NewVector = V2Add(SomeV2, TestV2); 162 | //... 163 | for(unsigned int i = 0; i <= 9 && i <= DebugLiveCount; ++i) { 164 | if(WasPressed(Numpad[i])) { 165 | debug_variable Var = &DebugLiveVariables[i+1]; 166 | switch(Var->Type) { 167 | //... 168 | } 169 | ChangesMade = 1; 170 | } 171 | } 172 | if(ChangesMade) DebugLiveVar_RewriteConfig("path/to/main_live.h"); 173 | 174 | ``` 175 | 176 | ## `hierarchy.h` 177 | Casey does implement a hierarchy (e.g. [Day 194: Organizing Debug Variables into a Hierarchy](https://hero.handmade.network/episode/code/day194/)), 178 | but if I remember right it's fairly dependent on its context. 179 | I don't think I referenced it when writing my own. 180 | 181 | ``` c 182 | #define DEBUG_HIERARCHY_KINDS \ 183 | DEBUG_HIERARCHY_KIND(debug_variable, Tweak) \ 184 | DEBUG_HIERARCHY_KIND(debug_variable, Watch) 185 | 186 | // See explanation under DEBUG_WATCH for DEBUG_ATOMIC_EXCHANGE. 187 | // Also, only needed if using the macros, otherwise thread safety is your responsibility! 188 | #define DEBUG_ATOMIC_EXCHANGE(ValuePtr, Desired) atomic_exchange(ValuePtr, Desired) 189 | #include "path/to/hierarchy.h" 190 | //... 191 | 192 | for(unsigned int i = 1; i <= DebugTweakCount; ++i) { 193 | debug_variable *Var = &DebugTweakVariables[i]; 194 | DebugHierarchy_InitElement(Var->Name, Var, DebugHierarchyKind_Tweak); 195 | } 196 | for(unsigned int i = 1; i <= DebugWatchCount; ++i) { 197 | debug_variable *Var = &DebugWatchVariables[i]; 198 | DebugHierarchy_InitElement(Var->Name, Var, DebugHierarchyKind_Watch); 199 | } 200 | 201 | for(debug_hierarchy_el *HVar = DebugHierarchy_Next(DebugHierarchy); 202 | HVar; 203 | HVar = DebugHierarchy_Next(HVar)) 204 | { 205 | // indent variables by same amount 206 | for(int Indent = DebugHierarchy_Level(*HVar); Indent; --Indent) { 207 | puts(" "); 208 | } 209 | 210 | switch(HVar->Kind) { 211 | case DebugHierarchyGroup: { 212 | printf("%.*s\n", HVar->NameLength, HVar->Name); 213 | if(SomeInputHappened) { 214 | HVar->IsClosed = ! HVar->IsClosed; // collapse and expand group 215 | } 216 | } break; 217 | 218 | case DebugHierarchyKind_Tweak: { 219 | debug_variable *Var = (debug_variable *)HVar.Data; 220 | InteractWithDebugVariable(Input, Var); 221 | WatchDebugVariable(OutputFormat, Var); 222 | } break; 223 | 224 | case DebugHierarchyKind_Watch: { 225 | debug_variable *Var = (debug_variable *)HVar.Data; 226 | WatchDebugVariable(OutputFormat, Var); 227 | } break; 228 | } 229 | } 230 | 231 | DEBUG_HIERARCHY_DECLARATION 232 | ``` 233 | 234 | ## Recommended libraries 235 | - All of the [STB single file libraries](https://github.com/nothings/stb) (he recommends others there as well) 236 | - My single file testing library - [`sweet.h`](https://github.com/azmr/sweet) 237 | - My single file header fonts - _Coming soon_ 238 | -------------------------------------------------------------------------------- /live_variable.h: -------------------------------------------------------------------------------- 1 | /* NOTE: `#if NO_VA_ARGS == 0` is for e.g. C89, where __VA_ARGS__ is not available -> more work needed to make structs live 2 | TODO: 3 | - add known types first time (like DEBUG_LIVE_IF) 4 | - add C basic types? 5 | - add "Leave Empty" comment under macro lists 6 | - allow types to be specified elsewhere? 7 | - watch pointers & strings 8 | - construct format strings from e.g. nested structs 9 | - define something to return to normal behaviour 10 | - also capture __FILE__ and __LINE__ 11 | */ 12 | 13 | /* allow for va_args to have commas replaced */ 14 | #define _ , 15 | 16 | #ifndef LIVE_VARIABLE_H 17 | #include 18 | 19 | #ifndef DEBUG_ATOMIC_EXCHANGE /* user can define it to e.g. C11 atomic_exchange or Windows InterlockedExchange */ 20 | /* sets bool to 1, evaluates to previous value of bool */ 21 | #define DEBUG_ATOMIC_EXCHANGE(ptr, desired) (*(ptr) ? 1 : (*(ptr))++) 22 | #endif 23 | 24 | typedef struct debug_variable debug_variable; 25 | 26 | typedef int debug_if; 27 | #define DEBUG_VAR_DECLARE(type, Name) static type DEBUG_LIVE_VAR_## Name 28 | #define DEBUG_LIVE_if(Name) if(DEBUG_LIVE_VAR_## Name) 29 | 30 | /* Declare the if statement variables */ 31 | #define DEBUG_LIVE_IF(Name, init) DEBUG_VAR_DECLARE(debug_if, Name); 32 | #if NO_VA_ARGS 33 | #define DEBUG_LIVE_TWEAK(type, Name, init) 34 | #else 35 | #define DEBUG_LIVE_TWEAK(type, Name, ...) 36 | #endif/*NO_VA_ARGS*/ 37 | DEBUG_LIVE_VARS 38 | #undef DEBUG_LIVE_TWEAK 39 | #undef DEBUG_LIVE_IF 40 | /* from now on, treat ifs like other vars */ 41 | #define DEBUG_LIVE_IF(Name, init) DEBUG_LIVE_TWEAK(debug_if, Name, init) 42 | 43 | #define LIVE_VARIABLE_H 44 | #endif/*LIVE_VARIABLE_H*/ 45 | 46 | /* WATCHED ******************************************************************/ 47 | /* Usage: int X = 6; => DEBUG_WATCH(int, X) = 6; */ 48 | #ifndef DEBUG_WATCHED_H 49 | #define DEBUG_WATCH_COUNTER __COUNTER__ 50 | debug_variable DebugWatchVariables[]; 51 | static unsigned int DebugWatchCount = 0; 52 | static unsigned int DebugWatchArrayLen; 53 | 54 | /* allows for the proper expansion */ 55 | #define DEBUG_WATCH_STR(x) #x 56 | 57 | #define DEBUG_WATCH_DEF_INIT(type, name, ...) DEBUG_WATCHED_DEF_INIT_(type, #name, name, __VA_ARGS__) 58 | #define DEBUG_WATCHED_DEF_INIT(type, path, var, ...) DEBUG_WATCHED_DEF_INIT_(type, DEBUG_WATCH_STR(path ##_## var), var, __VA_ARGS__) 59 | #define DEBUG_WATCH_DEF_EQ(type, name, ...) DEBUG_WATCH_DEF_N (type, name, 0) { type DebugWatchTemp_## var = __VA_ARGS__; var = DebugWatchTemp_## var; } 60 | #define DEBUG_WATCHED_DEF_EQ(type, path, var, ...) DEBUG_WATCHED_DEF_N (type, path, var, 0) { type DebugWatchTemp_## var = __VA_ARGS__; var = DebugWatchTemp_## var; } 61 | #define DEBUG_WATCH_DEF_ARR(type, name) DEBUG_WATCH_DEF_N (type, name, sizeof(name)/sizeof(*(name))) 62 | #define DEBUG_WATCHED_DEF_ARR(type, path, var) DEBUG_WATCHED_DEF_N (type, path, var, sizeof(var)/sizeof(*(var))) 63 | #define DEBUG_WATCH_DEF_N(type, name, n) DEBUG_WATCHED_DEF_N_ (type, #name, name, n) 64 | #define DEBUG_WATCHED_DEF_N(type, path, var, n) DEBUG_WATCHED_DEF_N_ (type, DEBUG_WATCH_STR(path ##_## var), var, n) 65 | 66 | /* minimal initialization - sets the value every time through */ 67 | #define DEBUG_WATCHED_DEF_N_(type, name, var, n) \ 68 | if(! DEBUG_ATOMIC_EXCHANGE(&DebugWatch_## var ##_IsInit, 1)) { \ 69 | debug_variable DebugWatch = { DebugVarType_## type, name, &var, n }; \ 70 | DebugWatchVariables[++DebugWatchCount] = DebugWatch; \ 71 | DEBUG_WATCH_COUNTER; \ 72 | } 73 | /* sets the value once on initialization, allows later tweaking that's lost on recompile */ 74 | #define DEBUG_WATCHED_DEF_INIT_(type, name, var, ...) \ 75 | if(! DEBUG_ATOMIC_EXCHANGE(&DebugWatch_## var ##_IsInit, 1)) { \ 76 | debug_variable DebugWatch = { DebugVarType_## type, name, &var }; \ 77 | type DebugWatchTemp_## var = __VA_ARGS__; \ 78 | var = DebugWatchTemp_## var; \ 79 | DebugWatchVariables[++DebugWatchCount] = DebugWatch; \ 80 | DEBUG_WATCH_COUNTER; \ 81 | } 82 | 83 | /* separating declaration and definition allows use without declare-anywhere */ 84 | #define DEBUG_WATCH_DECL(type, name) static type name; static int DebugWatch_## name ##_IsInit 85 | #define DEBUG_WATCH_DECL_ARR(type, name, n) static type name n; static int DebugWatch_## name ##_IsInit 86 | #define DEBUG_WATCH_DEF(type, name) DEBUG_WATCH_DEF_INIT(type, name, {0}) 87 | #define DEBUG_WATCHED_DEF(type, name, var) DEBUG_WATCHED_DEF_INIT(type, name, var, {0}) 88 | 89 | /* typical use: */ 90 | #define DEBUG_WATCH(type, name) DEBUG_WATCH_DECL(type, name); DEBUG_WATCH_DEF(type, name) name 91 | #define DEBUG_WATCHED(type, path, name) DEBUG_WATCH_DECL(type, name); DEBUG_WATCHED_DEF(type, path, name) name 92 | #define DEBUG_WATCH_INIT(type, name, ...) DEBUG_WATCH_DECL(type, name); DEBUG_WATCH_DEF_INIT(type, name, __VA_ARGS__) 93 | #define DEBUG_WATCHED_INIT(type, path, name, ...) DEBUG_WATCH_DECL(type, name); DEBUG_WATCHED_DEF_INIT(type, path, name, __VA_ARGS__) 94 | #define DEBUG_WATCH_EQ(type, name, ...) DEBUG_WATCH_DECL(type, name); DEBUG_WATCH_DEF_EQ(type, name, __VA_ARGS__) 95 | #define DEBUG_WATCHED_EQ(type, path, name, ...) DEBUG_WATCH_DECL(type, name); DEBUG_WATCHED_DEF_EQ(type, path, name, __VA_ARGS__) 96 | #define DEBUG_WATCH_ARR(type, name, n) DEBUG_WATCH_DECL_ARR(type, name, n); DEBUG_WATCH_DEF_ARR(type, name) 97 | #define DEBUG_WATCHED_ARR(type, path, name, n) DEBUG_WATCH_DECL_ARR(type, name, n); DEBUG_WATCHED_DEF_ARR(type, path, name) 98 | /* ^^^ suffixing with the name has the feature that it doesn't trigger a warning if unused elsewhere */ 99 | 100 | #define DEBUG_WATCH_DECLARATION \ 101 | enum { DebugWatchArrayLenEnum = DEBUG_WATCH_COUNTER + 1 }; \ 102 | debug_variable DebugWatchVariables[DebugWatchArrayLenEnum]; \ 103 | static unsigned int DebugWatchArrayLen = DebugWatchArrayLenEnum; 104 | /* NOTE: 0 is free for invalid queries */ 105 | #define DEBUG_WATCHED_H 106 | #endif/*DEBUG_WATCHED_H*/ 107 | /* END WATCHED **************************************************************/ 108 | 109 | /* LOOP *********************************************************************/ 110 | /* Usage: for(int i = 0; i < n; ++i) => DEBUG_for(int, i = 0, i < n, ++i) */ 111 | #ifndef DEBUG_LOOP_H 112 | 113 | #define DEBUG_FOR_CAT1(a, b) a ## b 114 | #define DEBUG_FOR_CAT2(a, b) DEBUG_FOR_CAT1(a, b) 115 | 116 | #if NO_DEBUG_MACROS || NO_LOOP_MACROS 117 | #define FRAME_for(type, init, condition, iterate) \ 118 | for(type init; condition; iterate) 119 | #else/*NO_DEBUG_MACROS || NO_LOOP_MACROS*/ 120 | 121 | #define DEBUG_for(type, init, condition, iterate) DEBUG_TOGGLE_for(type, init, condition, iterate, 1) 122 | #define DEBUG_TOGGLE_for(type, init, condition, iterate, isdebug) \ 123 | static type init; \ 124 | { \ 125 | static int DebugFor_IsInit; \ 126 | int IsDebug = isdebug; \ 127 | if(! DEBUG_ATOMIC_EXCHANGE(&DebugFor_IsInit, 1)) { init; } \ 128 | if(IsDebug) { goto DEBUG_FOR_CAT1(debug_for_label_, __LINE__); } \ 129 | } \ 130 | for(init; !isdebug && (condition); iterate) \ 131 | DEBUG_FOR_CAT1(debug_for_label_, __LINE__): 132 | 133 | #endif/*NO_DEBUG_MACROS || NO_LOOP_MACROS*/ 134 | 135 | #define DEBUG_LOOP_H 136 | #endif/*DEBUG_LOOP_H*/ 137 | /* END LOOP *****************************************************************/ 138 | 139 | /* TWEAK **********************************************************************/ 140 | /* TODO: should I include the function definitions above? */ 141 | #if defined(DEBUG_TWEAK_IMPLEMENTATION) && !defined(DEBUG_TWEAK_IMPLEMENTATION_H) 142 | #ifndef DEBUG_TYPES 143 | #error You have to #define DEBUG_TYPES! Do so in this format: `DEBUG_TYPE(type, printf_format_string)` 144 | #endif 145 | 146 | /* DEBUG_TYPE(Unknown, "%d") \ */ 147 | #define DEBUG_TWEAK_INTERNAL_TYPES \ 148 | DEBUG_TYPE(debug_if, "%u") \ 149 | 150 | #define DEBUG_TWEAK_TYPES \ 151 | DEBUG_TWEAK_INTERNAL_TYPES \ 152 | DEBUG_TYPES \ 153 | 154 | /* //// Debug variable type, name and pointer to value */ 155 | #if NO_VA_ARGS 156 | #define DEBUG_TYPE_STRUCT(type, format, init) DEBUG_TYPE(type, format) 157 | #else 158 | #define DEBUG_TYPE_STRUCT(type, format, ...) DEBUG_TYPE(type, format) 159 | #endif/*NO_VA_ARGS */ 160 | typedef struct debug_variable 161 | { 162 | enum { 163 | #define DEBUG_TYPE(type, format) \ 164 | DebugVarType_## type, 165 | DEBUG_TWEAK_TYPES 166 | #undef DEBUG_TYPE 167 | } Type; 168 | char *Name; 169 | void *Data; 170 | unsigned int N; /* if an array, contains the number of members in it; otherwise == 0 */ 171 | } debug_variable; 172 | #undef DEBUG_TYPE_STRUCT 173 | 174 | /* //// Reference debug array instances by name */ 175 | enum { 176 | DebugVarIndexEmpty, 177 | #if NO_VA_ARGS 178 | #define DEBUG_LIVE_TWEAK(type, Name, init) DebugVarIndex_## Name, 179 | #else 180 | #define DEBUG_LIVE_TWEAK(type, Name, ...) DebugVarIndex_## Name, 181 | #endif/*NO_VA_ARGS */ 182 | DEBUG_LIVE_VARS 183 | #undef DEBUG_LIVE_TWEAK 184 | DebugTweakCountPlusOne, 185 | DebugTweakCount = DebugTweakCountPlusOne - 1, 186 | }; 187 | 188 | 189 | /* //// Global variables of the right type */ 190 | #if NO_VA_ARGS 191 | #define DEBUG_LIVE_TWEAK(type, Name, init) DEBUG_VAR_DECLARE(type, Name) = init; 192 | #else 193 | #define DEBUG_LIVE_TWEAK(type, Name, ...) DEBUG_VAR_DECLARE(type, Name) = __VA_ARGS__; 194 | #endif/*!NO_VA_ARGS */ 195 | DEBUG_LIVE_VARS 196 | #undef DEBUG_LIVE_TWEAK 197 | 198 | /* //// Array of annotated typed pointers to global vars */ 199 | debug_variable DebugTweakVariables[] = { 200 | {0}, 201 | #if NO_VA_ARGS 202 | #define DEBUG_LIVE_TWEAK(type, Name, init) { DebugVarType_## type, #Name, (void *)&DEBUG_LIVE_VAR_## Name }, 203 | #else 204 | #define DEBUG_LIVE_TWEAK(type, Name, ...) { DebugVarType_## type, #Name, (void *)&DEBUG_LIVE_VAR_## Name }, 205 | #endif/*NO_VA_ARGS */ 206 | DEBUG_LIVE_VARS 207 | #undef DEBUG_LIVE_TWEAK 208 | }; 209 | 210 | static int DebugLiveVar_IsCompiling; 211 | 212 | /* returns the result of the system call (where 0 is success), or 0 if no action taken */ 213 | static int 214 | DebugLiveVar_Recompile(char *BuildCall) 215 | { 216 | int Result = 0; 217 | /* if(! DebugLiveVar_IsCompiling) */ 218 | /* if(! (DebugLiveVar_IsCompiling == 1 ? 1 : DebugLiveVar_IsCompiling++)) */ 219 | if(! DEBUG_ATOMIC_EXCHANGE(&DebugLiveVar_IsCompiling, 1)) 220 | { 221 | /* DebugLiveVar_IsCompiling = 1; */ 222 | Result = system(BuildCall); 223 | } 224 | return Result; 225 | } 226 | 227 | /* returns 1 if all file prints were successful */ 228 | static int 229 | DebugLiveVar_RewriteDefines(char *Filename) 230 | { 231 | int Result = 1; 232 | if(! DebugLiveVar_IsCompiling) 233 | { 234 | FILE *File = fopen(Filename, "w"); 235 | Result &= fprintf(File, "#ifndef LIVE_VAR_DEFINES_H\n") > 0; 236 | for(u32 iVar = 0; iVar < ArrayCount(DebugTweakVariables); ++iVar) 237 | { 238 | debug_variable Var = DebugTweakVariables[iVar]; 239 | switch(Var.Type) 240 | { 241 | #if NO_VA_ARGS 242 | #define DEBUG_TYPE_STRUCT(type, format, init) case DebugVarType_## type: Result &= fprintf(File, "#define DEBUGVAR_%s "format"\n", Var.Name, init) > 0; break; 243 | #else 244 | #define DEBUG_TYPE_STRUCT(type, format, ...) case DebugVarType_## type: Result &= fprintf(File, "#define DEBUGVAR_%s "format"\n", Var.Name, __VA_ARGS__) > 0; break; 245 | #endif/*NO_VA_ARGS*/ 246 | #define DEBUG_TYPE_MEMBER(struct, member) ((struct *)Var.Data)->member 247 | #define DEBUG_TYPE(type, format) case DebugVarType_## type: Result &= fprintf(File, "#define DEBUGVAR_%s "format"\n", Var.Name, *(type *)Var.Data) > 0; break; 248 | DEBUG_TYPES 249 | #undef DEBUG_TYPE 250 | #undef DEBUG_TYPE_STRUCT 251 | #undef DEBUG_TYPE_MEMBER 252 | } 253 | } 254 | Result &= fprintf(File, "#define LIVE_VAR_DEFINES_H\n#endif") > 0; 255 | fclose(File); 256 | } 257 | return Result; 258 | } 259 | 260 | /* returns 1 if all file prints were successful */ 261 | /* TODO: make more obvious that nothing will happen if currently compiling */ 262 | static int 263 | DebugLiveVar_RewriteConfig(char *Filename) 264 | { 265 | int Result = 1; 266 | if(! DebugLiveVar_IsCompiling) 267 | { 268 | FILE *File = fopen(Filename, "w"); 269 | Result &= fprintf(File, "#ifndef LIVE_VAR_CONFIG_H\n\n#define DEBUG_LIVE_VARS \\\n") > 0; 270 | 271 | for(u32 iVar = 1; iVar < ArrayCount(DebugTweakVariables); ++iVar) 272 | { 273 | debug_variable Var = DebugTweakVariables[iVar]; 274 | switch(Var.Type) 275 | { 276 | case DebugVarType_debug_if: Result &= fprintf(File, "\tDEBUG_LIVE_IF(%s, %u) \\\n", Var.Name, *(debug_if *)Var.Data) > 0; break; 277 | 278 | #define DEBUG_TYPE_MEMBER(struct, member) ((struct *)Var.Data)->member 279 | #if NO_VA_ARGS 280 | #define DEBUG_TYPE_STRUCT(type, format, init) case DebugVarType_## type: Result &= fprintf(File, "\tDEBUG_LIVE_TWEAK("#type", %s, "format") \\\n", Var.Name, init) > 0; break; 281 | #else 282 | #define DEBUG_TYPE_STRUCT(type, format, ...) case DebugVarType_## type: Result &= fprintf(File, "\tDEBUG_LIVE_TWEAK("#type", %s, "format") \\\n", Var.Name, __VA_ARGS__) > 0; break; 283 | #endif/*NO_VA_ARGS */ 284 | #define DEBUG_TYPE(type, format) case DebugVarType_## type: Result &= fprintf(File, "\tDEBUG_LIVE_TWEAK("#type", %s, "format") \\\n", Var.Name, *(type *)Var.Data) > 0; break; 285 | DEBUG_TYPES 286 | #undef DEBUG_TYPE 287 | #undef DEBUG_TYPE_STRUCT 288 | #undef DEBUG_TYPE_MEMBER 289 | } 290 | } 291 | Result &= fputs("\n\n#include \""__FILE__"\"\n#define LIVE_VAR_CONFIG_H\n#endif", File) >= 0; 292 | fclose(File); 293 | } 294 | return Result; 295 | } 296 | #define DEBUG_TWEAK_IMPLEMENTATION_H 297 | #endif/*DEBUG_TWEAK_IMPLEMENTATION && !DEBUG_TWEAK_IMPLEMENTATION_H*/ 298 | 299 | #undef _ 300 | --------------------------------------------------------------------------------