From 1ae4a5fef90338faf324b39c0e82441c45ca3090 Mon Sep 17 00:00:00 2001 From: Ledda Date: Sat, 11 Jan 2025 15:10:56 +0000 Subject: [PATCH] update --- app.cpp | 211 ++++++++++++++++++++++++++++++++---------- build.bat | 5 +- djstdlib/core.cpp | 25 ++++- djstdlib/core.h | 7 ++ djstdlib/os_linux.cpp | 1 + djstdlib/os_win32.cpp | 2 +- 6 files changed, 195 insertions(+), 56 deletions(-) diff --git a/app.cpp b/app.cpp index 0e9b015..e93df66 100644 --- a/app.cpp +++ b/app.cpp @@ -60,7 +60,41 @@ GymLogDbParsed *parseDb(Arena *arena, string database) { return dbParsed; } -int gymTrackerWorkForExercise(Arena *arena, uint32 exerciseId) { +list loadEntryLog(Arena *arena, string fileLocation) { + list result = {0}; + string logfile = readEntireFile(arena, LOG_FILE_LOCATION); + + if (logfile.length % sizeof(GymLogEntry) != 0) { + log("Log file corrupted.\n"); + } else { + size_t entryCount = logfile.length / sizeof(GymLogEntry); + result = { (GymLogEntry *)logfile.str, entryCount, entryCount }; + } + + return result; +} + +struct WorkSummary { + real32 totalWork; + uint32 restTime; +}; + +WorkSummary workSummaryForExercise(list entries) { + WorkSummary result = {0}; + UnixTimestamp lastTimestamp = 0; + for (EachInReversed(entries, i)) { + GymLogEntry logEntry = entries.data[i]; + result.totalWork += logEntry.weightRepsInfo.weight * logEntry.weightRepsInfo.reps; + if (lastTimestamp > 0) { + result.restTime += (uint32)(lastTimestamp - logEntry.timestamp); + } + lastTimestamp = logEntry.timestamp; + } + + return result; +} + +int gymTrackerWorkToday(Arena *arena, uint32 exerciseId, string exerciseName) { int statusCode = 0; string logfile = readEntireFile(arena, LOG_FILE_LOCATION); @@ -69,23 +103,27 @@ int gymTrackerWorkForExercise(Arena *arena, uint32 exerciseId) { statusCode = 1; } else { size_t entryCount = logfile.length / sizeof(GymLogEntry); - int currentDay = 0; - int currentYear = 0; list logEntries = { (GymLogEntry *)logfile.str, entryCount, entryCount }; UnixTimestamp todayUnix = getSystemUnixTime(); Timestamp todayTs = timestampFromUnixTime(&todayUnix); - - real32 work = 0; - for (EachIn(logEntries, i)) { + list todaysEntries = {0}; + todaysEntries.data = logEntries.data; + for (EachInReversed(logEntries, i)) { GymLogEntry logEntry = logEntries.data[i]; Timestamp logTs = timestampFromUnixTime(&logEntry.timestamp); if (logTs.tm_yday == todayTs.tm_yday && todayTs.tm_year == logTs.tm_year) { - work += logEntry.weightRepsInfo.weight * logEntry.weightRepsInfo.reps; + todaysEntries.head += 1; + todaysEntries.data = &logEntries.data[i + 1]; } } + + if (todaysEntries.data) { + todaysEntries.length = todaysEntries.head; + WorkSummary summary = workSummaryForExercise(todaysEntries); + log("Total work today for %S:\n%.2fkg in ~%.2fmin.\n", exerciseName, summary.totalWork, (real32)summary.restTime / 60.0f); + } - log("Total work for this exercise today:\n%.2f J * m/ex\n", work); } return statusCode; @@ -101,94 +139,115 @@ int gymTrackerStatus(Arena *arena, list args) { puts("Log file corrupted."); statusCode = 1; } else { - Timestamp stopTs = {0}; - if (args.length > 0 && strEql(args.data[0], "--today"_s)) { - UnixTimestamp nowUnix = getSystemUnixTime(); - stopTs = timestampFromUnixTime(&nowUnix); - } else if (args.length > 1 && strEql(args.data[0], "--days"_s) || strEql(args.data[0], "-d"_s)) { - size_t l; - int numDays = parsePositiveInt(args.data[1], &l); + Timestamp startTs = {0}; + int numDays = 1; + bool showAll = args.length == 1 && strEql(args.data[0], "--all"_s); + if (!showAll) { + if (args.length == 2 && (strEql(args.data[0], "--days"_s) || strEql(args.data[0], "-d"_s))) { + size_t l; + numDays = parsePositiveInt(args.data[1], &l); + } if (numDays == -1) { - puts("Bad argument for --days parameter."); + puts("Bad argument for --days (-d) parameter."); statusCode = 1; } else { uint64 todayUnix = getSystemUnixTime(); - UnixTimestamp stopUnix = todayUnix - numDays * 24 * 60 * 60; - stopTs = timestampFromUnixTime(&stopUnix); + UnixTimestamp startUnix = todayUnix - numDays * 24 * 60 * 60; + startTs = timestampFromUnixTime(&startUnix); } } if (statusCode == 0) { + int lastDay = -1; + int lastYear = -1; size_t entryCount = file.length / sizeof(GymLogEntry); - int currentDay = -1; - int currentYear = -1; list logEntries = { (GymLogEntry *)file.str, entryCount, entryCount }; - list workPerExerciseByDay = PushFullList(arena, real32, db->header.nextId); - list nameByExercise = PushFullList(arena, string, db->header.nextId); - zeroListFull(&workPerExerciseByDay); - zeroListFull(&nameByExercise); + list nameByExercise = PushFullListZero(arena, string, db->header.nextId); + + list workPerExerciseByDay = PushFullListZero(arena, real32, db->header.nextId); + list restPerExerciseByDay = PushFullListZero(arena, uint32, db->header.nextId); + list lastTsPerExerciseByDay = PushFullListZero(arena, uint32, db->header.nextId); int dayCount = 0; Timestamp timestamp = {0}; - if (logEntries.length > 0) { - timestamp = timestampFromUnixTime(&logEntries.data[logEntries.length - 1].timestamp); - } - for (EachInReversed(logEntries, i)) { - GymLogEntry entry = logEntries.data[i]; - if (timestamp.tm_yday != currentDay || timestamp.tm_year != currentYear) { - if (timestamp.tm_year < stopTs.tm_year || timestamp.tm_yday < stopTs.tm_yday) { - break; - } + GymLogEntry *prevEntry = 0; + GymLogEntry *entry = 0; + for (EachIn(logEntries, i)) { + prevEntry = entry; + entry = &logEntries.data[i]; + + timestamp = timestampFromUnixTime(&entry->timestamp); + + if (timestamp.tm_year < startTs.tm_year || timestamp.tm_yday < startTs.tm_yday) { + continue; + } + + if (timestamp.tm_yday != lastDay || timestamp.tm_year != lastYear) { if (dayCount > 0) { log("\n"); } - log("--- %S ---\n", formatTimeYmd(arena, ×tamp)); - currentDay = timestamp.tm_yday; - currentYear = timestamp.tm_year; + log("================ %S =================================\n", formatTimeYmd(arena, ×tamp)); + lastDay = timestamp.tm_yday; + lastYear = timestamp.tm_year; dayCount++; } - workPerExerciseByDay.data[entry.exerciseId] += entry.weightRepsInfo.reps * entry.weightRepsInfo.weight; + workPerExerciseByDay.data[entry->exerciseId] += entry->weightRepsInfo.reps * entry->weightRepsInfo.weight; + uint32 lastTsForExercise = lastTsPerExerciseByDay.data[entry->exerciseId]; + if (lastTsForExercise > 0) { + restPerExerciseByDay.data[entry->exerciseId] += (uint32)(entry->timestamp - lastTsForExercise); + } + lastTsPerExerciseByDay.data[entry->exerciseId] = (uint32)entry->timestamp; const char *format; - if (entry.weightRepsInfo.weight == (int32)entry.weightRepsInfo.weight) { - format = "%S: %S, %.0fkg X %i\n"; + if (entry->weightRepsInfo.weight == (int32)entry->weightRepsInfo.weight) { + format = "%S: %S %.0fkg X %i\n"; } else { - format = "%S: %S, %.2fkg X %i\n"; + format = "%S: %S %.2fkg X %i\n"; } - string *exerciseName = &(nameByExercise.data[entry.exerciseId]); + string *exerciseName = &(nameByExercise.data[entry->exerciseId]); if (exerciseName->str == 0) { for (EachIn(db->entries, j)) { GymLogDbParsedEntry dbEntry = db->entries.data[j]; - if (dbEntry.id == entry.exerciseId) { + if (dbEntry.id == entry->exerciseId) { *exerciseName = dbEntry.name; } } } + string nameToPrint = {0}; + if (prevEntry && entry->exerciseId == prevEntry->exerciseId) { + nameToPrint = PushStringFill(arena, exerciseName->length, '.'); + } else { + nameToPrint = exerciseName->str ? *exerciseName : "unknown-exercise"_s; + log("\n"); + } log(format, formatTimeHms(arena, ×tamp), - exerciseName->str ? *exerciseName : "unknown-exercise"_s, - entry.weightRepsInfo.weight, - entry.weightRepsInfo.reps); + nameToPrint, + entry->weightRepsInfo.weight, + entry->weightRepsInfo.reps); - if (i > 0) { - timestamp = timestampFromUnixTime(&logEntries.data[i - 1].timestamp); + Timestamp nextTimestamp = {0}; + if (i < logEntries.head - 1) { + nextTimestamp = timestampFromUnixTime(&logEntries.data[i + 1].timestamp); } - if (i == 0 || timestamp.tm_yday != currentDay || timestamp.tm_year != currentYear) { + if (i == logEntries.head + 1 || nextTimestamp.tm_yday != lastDay || nextTimestamp.tm_year != lastYear) { log("\n"); log("Work summary:\n"); for (size_t j = 0; j < workPerExerciseByDay.length; j++) { if (workPerExerciseByDay.data[j] != 0.0f) { - log("%S: %.2f J * m/ex\n", nameByExercise.data[j], workPerExerciseByDay.data[j]); + log("%S: %.2fkg in %.2fmin\n", nameByExercise.data[j], workPerExerciseByDay.data[j], (real32)restPerExerciseByDay.data[j] / 60.0f); } } zeroListFull(&workPerExerciseByDay); + zeroListFull(&restPerExerciseByDay); + zeroListFull(&lastTsPerExerciseByDay); } } } @@ -197,6 +256,32 @@ int gymTrackerStatus(Arena *arena, list args) { return statusCode; } +int gymTrackerDeleteEntries(Arena *arena, list args) { + int statusCode = 0; + + if (args.length == 0) { + log("Please pass the number of entries to delete starting from the most recent."); + statusCode = 1; + } else { + size_t position = 0; + int numToDelete = parsePositiveInt(args.data[0], &position); + if (numToDelete != -1) { + list logEntries = loadEntryLog(arena, LOG_FILE_LOCATION); + if (numToDelete > logEntries.length) { + log("%i is more than the current number of log entries (%i). Aborting.", numToDelete, logEntries.length); + statusCode = 1; + } else { + writeEntireFile(arena, LOG_FILE_LOCATION, (byte *)logEntries.data, (logEntries.length - numToDelete) * sizeof(GymLogEntry)); + } + } else { + log("Invalid number to delete.\n"); + statusCode = 0; + } + } + + return statusCode; +} + // Syntax: do weightKg reps int gymTrackerDo(Arena *arena, list args) { int statusCode = 0; @@ -214,12 +299,14 @@ int gymTrackerDo(Arena *arena, list args) { GymLogDbParsed *db = parseDb(arena, readEntireFile(arena, DB_FILE_LOCATION)); for (EachIn(db->entries, i)) { GymLogDbParsedEntry entry = db->entries.data[i]; - if (strEql(entry.name, newExerciseName)) { + if (strStartsWith(entry.name, newExerciseName)) { existingEntry = &entry; + log("Assuming exercise \"%S\".\n\n", entry.name); break; } } if (!existingEntry) { + log("The exercise \"%S\" hasn't been registered.", newExerciseName); statusCode = 1; } } @@ -241,7 +328,23 @@ int gymTrackerDo(Arena *arena, list args) { }; fileAppend(arena, LOG_FILE_LOCATION, (byte *)&entry, sizeof(entry)); - statusCode = gymTrackerWorkForExercise(arena, exerciseId); + statusCode = gymTrackerWorkToday(arena, exerciseId, newExerciseName); + } + } + + return statusCode; +} + +int gymTrackerListExercises(Arena *arena, list args) { + int statusCode = 0; + GymLogDbParsed *db = parseDb(arena, readEntireFile(arena, DB_FILE_LOCATION)); + + if (db->entries.length == 0) { + log("No entries currently registered in the exercise database."); + } else { + log("%i entries currently registered in the exercise database:\n\n", db->entries.length); + for (EachIn(db->entries, i)) { + log("#%i: %S\n", i + 1, db->entries.data[i].name); } } @@ -250,6 +353,8 @@ int gymTrackerDo(Arena *arena, list args) { int gymTrackerAddExercise(Arena *arena, list args) { int statusCode = 0; + GymLogDbParsed *db = parseDb(arena, readEntireFile(arena, DB_FILE_LOCATION)); + string newExerciseName = args.data[0]; if (newExerciseName.length == 0) { log("No exercise name provided.\n"); @@ -329,6 +434,10 @@ int main(int argc, char **argv) { statusCode = gymTrackerStatus(arena, listSlice(args, 1)); } else if (strEql(args.data[0], "do"_s)) { statusCode = gymTrackerDo(arena, listSlice(args, 1)); + } else if (strEql(args.data[0], "delete"_s)) { + statusCode = gymTrackerDeleteEntries(arena, listSlice(args, 1)); + } else if (strEql(args.data[0], "list"_s)) { + statusCode = gymTrackerListExercises(arena, listSlice(args, 1)); } else if (strEql(args.data[0], "add"_s)) { statusCode = gymTrackerAddExercise(arena, listSlice(args, 1)); } else { diff --git a/build.bat b/build.bat index 6557a97..4d507fb 100644 --- a/build.bat +++ b/build.bat @@ -5,13 +5,14 @@ if NOT EXIST .\target mkdir .\target set commonLinkerFlags=-opt:ref set commonCompilerFlags=^ -MT %= Make sure the C runtime library is statically linked =%^ - -Gm- %= Turns off incremently building =%^ + -Gm- %= Turns off incremental building =%^ -nologo %= No one cares you made the compiler Microsoft =%^ -Oi %= Always use intrinsics =%^ -EHa- %= Disable exception handling =%^ -GR- %= Never use runtime type info from C++ =%^ -WX -W4 -wd4201 -wd4100 -wd4189 -wd4505 %= Compiler warnings, -WX warnings as errors, -W4 warning level 4, -wdXXXX disable warning XXXX =%^ - -DAPP_DEBUG=0 -DSLOWMODE=1 -DENVIRONMENT_WINDOWS=1 %= Custom #defines =%^ + -DAPP_DEBUG=0 -DENABLE_ASSERT=1 -DOS_WINDOWS=1 %= Custom #defines =%^ + -D_CRT_SECURE_NO_WARNINGS=1^ -FC %= Full path of source code file in diagnostics =%^ -Zi %= Generate debugger info =% diff --git a/djstdlib/core.cpp b/djstdlib/core.cpp index ccea028..12956e9 100644 --- a/djstdlib/core.cpp +++ b/djstdlib/core.cpp @@ -1,11 +1,20 @@ +#include "os.cpp" #include #include -#include #include "core.h" -#include "os.cpp" #define STB_SPRINTF_IMPLEMENTATION #include "vendor/stb_sprintf.h" +void *pushSizeFill(Arena *arena, size_t bytes, byte fill) { + if (arena->capacity - arena->head >= bytes) { + void *ptr = (char *)arena->memory + arena->head; + arena->head += bytes; + memset(ptr, fill, bytes); + return ptr; + } + return 0; +} + void *pushSize(Arena *arena, size_t bytes) { if (arena->capacity - arena->head >= bytes) { void *ptr = (char *)arena->memory + arena->head; @@ -112,6 +121,18 @@ const char *cstring(Arena *arena, string str) { return arr; } +bool strStartsWith(string str, string testStr) { + if (str.length < testStr.length) { + return false; + } + for (size_t i = 0; i < testStr.length; i++) { + if (str.str[i] != testStr.str[i]) { + return false; + } + } + return true; +} + bool strEql(string s1, string s2) { if (s1.length != s2.length) { return false; diff --git a/djstdlib/core.h b/djstdlib/core.h index 5e796e7..3654df1 100644 --- a/djstdlib/core.h +++ b/djstdlib/core.h @@ -57,6 +57,7 @@ struct Scratch { }; void *pushSize(Arena *arena, size_t bytes); +void *pushSizeFill(Arena *arena, size_t bytes, byte fill); Arena *arenaAlloc(size_t capacity); void arenaFree(Arena *arena); void arenaFreeFrom(Arena *arena, size_t pos); @@ -68,7 +69,9 @@ Scratch scratchStart(Arena **conflicts, size_t conflictCount); void scratchEnd(Scratch scratch); #define PushArray(arena, type, size) (type *)pushSize(arena, sizeof(type) * (size)) +#define PushArrayZero(arena, type, size) (type *)pushSizeFill(arena, sizeof(type) * (size), 0) #define PushStruct(arena, type) (type *)pushSize(arena, sizeof(type)) +#define PushStructZero(arena, type) (type *)pushSizeFill(arena, sizeof(type), 0) // ### Vectors ### template @@ -134,7 +137,9 @@ struct list { }; #define PushList(arena, type, size) (list{ PushArray(arena, type, size), size, 0 }) +#define PushListZero(arena, type, size) (list{ PushArrayZero(arena, type, size), size, 0 }) #define PushFullList(arena, type, size) (list{ PushArray(arena, type, size), size, size }) +#define PushFullListZero(arena, type, size) (list{ PushArrayZero(arena, type, size), size, size }) template T *appendList(list *list, T element); template void zeroList(list *list); @@ -151,6 +156,7 @@ struct string { #define strlit(lit) (string{(char *)(lit), sizeof(lit) - 1}) #define PushString(arena, length) (string{ (char *)pushSize(arena, length), (length) }) +#define PushStringFill(arena, length, characterByte) (string{ (char *)pushSizeFill(arena, length, characterByte), (length) }) string operator""_s(const char *cstrLiteral, size_t length); // C Strings @@ -160,6 +166,7 @@ size_t calcStringLen(const char *str); string strFromCString(Arena *arena, const char *str); bool strEql(string s1, string s2); +bool strStartsWith(string str, string testStr); bool stringContains(string str, char c); string strReverse(Arena *arena, string str); diff --git a/djstdlib/os_linux.cpp b/djstdlib/os_linux.cpp index 122a4bb..01f9330 100644 --- a/djstdlib/os_linux.cpp +++ b/djstdlib/os_linux.cpp @@ -5,6 +5,7 @@ #include #include +#include void *os_alloc(size_t capacity) { return mmap(0, capacity, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); diff --git a/djstdlib/os_win32.cpp b/djstdlib/os_win32.cpp index 01eb4ec..a255c8a 100644 --- a/djstdlib/os_win32.cpp +++ b/djstdlib/os_win32.cpp @@ -1,8 +1,8 @@ #ifndef OS_IMPL_WIN32_CPP #define OS_IMPL_WIN32_CPP -#include "os.h" #include "Windows.h" +#include "os.h" void *os_alloc(size_t commitSize) { return VirtualAlloc(NULL, commitSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);