Time of day, gamestate judgement memories and arena, factor out request platform specific code correctly, fix nullterm metadesk bug in winhttp request response body

main
parent fc70f8a043
commit 6225eb542f

226
main.c

@ -460,6 +460,14 @@ LPCWSTR windows_string(String8 s)
} }
#endif #endif
typedef enum
{
GEN_Deleted = -1,
GEN_NotDoneYet = 0,
GEN_Success = 1,
GEN_Failed = 2,
} GenRequestStatus;
#ifdef DESKTOP #ifdef DESKTOP
#ifdef WINDOWS #ifdef WINDOWS
#pragma warning(push, 3) #pragma warning(push, 3)
@ -549,7 +557,7 @@ void generation_thread(void* my_request_voidptr)
WinAssertWithErrorCode(WinHttpReadData(hRequest, (LPVOID)out_buffer, dwSize, &dwDownloaded)); WinAssertWithErrorCode(WinHttpReadData(hRequest, (LPVOID)out_buffer, dwSize, &dwDownloaded));
out_buffer[dwDownloaded - 1] = '\0'; out_buffer[dwDownloaded - 1] = '\0';
Log("Got this from http, size %lu: %s\n", dwDownloaded, out_buffer); Log("Got this from http, size %lu: %s\n", dwDownloaded, out_buffer);
S8ListPush(my_request->arena, &received_data_list, S8(out_buffer, dwDownloaded)); S8ListPush(my_request->arena, &received_data_list, S8(out_buffer, dwDownloaded - 1)); // the string shouldn't include a null terminator in its length, and WinHttpReadData has a null terminator here
} }
} while (dwSize > 0); } while (dwSize > 0);
String8 received_data = S8ListJoin(my_request->arena, received_data_list, &(StringJoin){0}); String8 received_data = S8ListJoin(my_request->arena, received_data_list, &(StringJoin){0});
@ -566,8 +574,10 @@ void generation_thread(void* my_request_voidptr)
} }
} }
int make_generation_request(String8 post_req_body) int make_generation_request(String8 prompt)
{ {
ArenaTemp scratch = GetScratch(0,0);
String8 post_req_body = FmtWithLint(scratch.arena, "|%.*s", S8VArg(prompt));
// checking for taken characters, pipe should only occur at the beginning // checking for taken characters, pipe should only occur at the beginning
for(u64 i = 1; i < post_req_body.size; i++) for(u64 i = 1; i < post_req_body.size; i++)
{ {
@ -599,6 +609,7 @@ int make_generation_request(String8 post_req_body)
DblPushBack(requests_first, requests_last, to_return); DblPushBack(requests_first, requests_last, to_return);
ReleaseScratch(scratch);
return to_return->id; return to_return->id;
} }
@ -625,12 +636,68 @@ void done_with_request(int id)
DblRemove(requests_first, requests_last, req); DblRemove(requests_first, requests_last, req);
StackPush(requests_free_list, req); StackPush(requests_free_list, req);
} }
GenRequestStatus gen_request_status(int id)
{
ChatRequest *req = get_by_id(id);
if(!req)
return GEN_Deleted;
else
return req->status;
}
TextChunk gen_request_content(int id)
{
assert(get_by_id(id));
return get_by_id(id)->generated;
}
#else #else
ISANERROR("Only know how to do desktop http requests on windows") ISANERROR("Only know how to do desktop http requests on windows")
#endif // WINDOWS #endif // WINDOWS
#endif // DESKTOP #endif // DESKTOP
#ifdef WEB
int make_generation_request(String8 prompt)
{
ArenaTemp scratch = GetScratch(0, 0);
String8 terminated_completion_url = nullterm(scratch.arena, FmtWithLint(scratch.arena, "%s://%s:%d/completion", IS_SERVER_SECURE ? "https" : "http", SERVER_DOMAIN, SERVER_PORT));
int req_id = EM_ASM_INT({
return make_generation_request(UTF8ToString($0, $1), UTF8ToString($2, $3));
},
prompt_str.str, (int)prompt_str.size, terminated_completion_url.str, (int)terminated_completion_url.size);
ReleaseScratch(scratch);
return req_id;
}
GenRequestStatus gen_request_status(int id)
{
int status = EM_ASM_INT({
return get_generation_request_status($0);
}, id);
return status;
}
TextChunk gen_request_content(int id)
{
char sentence_cstr[MAX_SENTENCE_LENGTH] = {0};
EM_ASM({
let generation = get_generation_request_content($0);
stringToUTF8(generation, $1, $2);
},
id, sentence_cstr, ARRLEN(sentence_cstr) - 1); // I think minus one for null terminator...
TextChunk to_return = {0};
memcpy(to_return.text, sentence_cstr, MAX_SENTENCE_LENGTH);
to_return.text_length = strlen(sentence_cstr);
return to_return;
}
void done_with_request(int id)
{
EM_ASM({
done_with_generation_request($0);
},
id);
}
#endif // WEB
Memory *memories_free_list = 0; Memory *memories_free_list = 0;
RememberedError *remembered_error_free_list = 0; RememberedError *remembered_error_free_list = 0;
@ -1740,6 +1807,14 @@ String8 is_action_valid(Arena *arena, Entity *from, Action a)
String8 error_message = (String8){0}; String8 error_message = (String8){0};
if(error_message.size == 0 && a.speech.text_length > 0)
{
if(S8FindSubstring(TextChunkString8(a.speech), S8Lit("assist"), 0, StringMatchFlag_CaseInsensitive) != a.speech.text_length)
{
error_message = S8Lit("You cannot use the word 'assist' in any form, you are not an assistant, do not act overtly helpful");
}
}
CanTalkTo talk = get_can_talk_to(from); CanTalkTo talk = get_can_talk_to(from);
if(error_message.size == 0 && a.talking_to_kind) if(error_message.size == 0 && a.talking_to_kind)
{ {
@ -1947,6 +2022,13 @@ bool perform_action(GameState *gs, Entity *from, Action a)
proceed_propagating = false; proceed_propagating = false;
} }
if(from->npc_kind == NPC_Daniel || a.talking_to_kind == NPC_Daniel)
{
Memory *new_forever = PushArray(gs->arena, Memory, 1);
*new_forever = make_memory(a, (MemoryContext){.author_npc_kind = from->npc_kind, .talking_to_kind = a.talking_to_kind});
StackPush(gs->judgement_memories, new_forever);
}
bool angel_heard_action = false; bool angel_heard_action = false;
Entity *targeted = 0; Entity *targeted = 0;
@ -2183,7 +2265,12 @@ void transition_to_room(GameState *gs, ThreeDeeLevel *level, String8 new_room_na
void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *level) void initialize_gamestate_from_threedee_level(GameState *gs, ThreeDeeLevel *level)
{ {
if(gs->arena)
{
ArenaRelease(gs->arena);
}
memset(gs, 0, sizeof(GameState)); memset(gs, 0, sizeof(GameState));
gs->arena = ArenaAlloc();
rnd_gamerand_seed(&gs->random, RANDOM_SEED); rnd_gamerand_seed(&gs->random, RANDOM_SEED);
// make entities for all rooms // make entities for all rooms
@ -3350,6 +3437,7 @@ String8 make_devtools_help(Arena *arena)
P("P - toggles spall profiling on/off, don't leave on for very long as it consumes a lot of storage if you do that. The resulting spall trace is saved to the file '%s'\n", PROFILING_SAVE_FILENAME); P("P - toggles spall profiling on/off, don't leave on for very long as it consumes a lot of storage if you do that. The resulting spall trace is saved to the file '%s'\n", PROFILING_SAVE_FILENAME);
P("If you hover over somebody it will display some parts of their memories, can be somewhat helpful\n"); P("If you hover over somebody it will display some parts of their memories, can be somewhat helpful\n");
P("P - immediately kills %s\n", characters[NPC_Raphael].name); P("P - immediately kills %s\n", characters[NPC_Raphael].name);
P("J - judges the player and outputs their verdict to the console\n");
#undef P #undef P
String8 to_return = S8ListJoin(arena, list, &(StringJoin){0}); String8 to_return = S8ListJoin(arena, list, &(StringJoin){0});
@ -5591,7 +5679,7 @@ TextPlacementSettings speech_bubble = {
String8List words_on_current_page(Entity *it, TextPlacementSettings *settings) String8List words_on_current_page(Entity *it, TextPlacementSettings *settings)
{ {
String8 last = last_said_sentence(it); String8 last = last_said_sentence(it);
PlacedWordList placed = place_wrapped_words(frame_arena, split_by_word(frame_arena, last), settings->text_scale, settings->width_in_pixels, *settings->font, JUST_LEFT); PlacedWordList placed = place_wrapped_words(frame_arena, split_by_word(frame_arena, last), settings->text_scale, settings->text_width_in_pixels, *settings->font, JUST_LEFT);
String8List on_current_page = {0}; String8List on_current_page = {0};
for(PlacedWord *cur = placed.first; cur; cur = cur->next) for(PlacedWord *cur = placed.first; cur; cur = cur->next)
@ -5669,6 +5757,8 @@ void frame(void)
{ {
uint64_t time_start_frame = stm_now(); uint64_t time_start_frame = stm_now();
gs.time += dt_double;
text_input_fade = Lerp(text_input_fade, unwarped_dt * 8.0f, receiving_text_input ? 1.0f : 0.0f); text_input_fade = Lerp(text_input_fade, unwarped_dt * 8.0f, receiving_text_input ? 1.0f : 0.0f);
Vec3 player_pos = V3(gs.player->pos.x, 0.0, gs.player->pos.y); Vec3 player_pos = V3(gs.player->pos.x, 0.0, gs.player->pos.y);
@ -5713,7 +5803,7 @@ void frame(void)
Vec3 light_dir; Vec3 light_dir;
{ {
float t = (float)(elapsed_time/3.0f - floor(elapsed_time/3.0f)); float t = clamp01((float)(gs.time / LENGTH_OF_DAY));
Vec3 sun_vector = V3(2.0f*t - 1.0f, sinf(t*PI32), 0.8f); // where the sun is pointing from Vec3 sun_vector = V3(2.0f*t - 1.0f, sinf(t*PI32), 0.8f); // where the sun is pointing from
light_dir = NormV3(MulV3F(sun_vector, -1.0f)); light_dir = NormV3(MulV3F(sun_vector, -1.0f));
} }
@ -6304,47 +6394,26 @@ void frame(void)
{ {
assert(it->gen_request_id > 0); assert(it->gen_request_id > 0);
#ifdef DESKTOP GenRequestStatus status = gen_request_status(it->gen_request_id);
int status = get_by_id(it->gen_request_id)->status; switch(status)
#else
#ifdef WEB
int status = EM_ASM_INT( {
return get_generation_request_status($0);
}, it->gen_request_id);
#else
ISANERROR("Don't know how to do this stuff on this platform.")
#endif // WEB
#endif // DESKTOP
if (status == 0)
{
// simply not done yet
}
else
{ {
if (status == 1) case GEN_Deleted:
it->gen_request_id = 0;
break;
case GEN_NotDoneYet:
break;
case GEN_Success:
{ {
having_errors = false; having_errors = false;
// done! we can get the string // done! we can get the string
char sentence_cstr[MAX_SENTENCE_LENGTH] = { 0 }; TextChunk sentence_chunk = gen_request_content(it->gen_request_id);
#ifdef WEB String8 sentence_str = TextChunkString8(sentence_chunk);
EM_ASM( {
let generation = get_generation_request_content($0);
stringToUTF8(generation, $1, $2);
}, it->gen_request_id, sentence_cstr, ARRLEN(sentence_cstr) - 1); // I think minus one for null terminator...
#endif
#ifdef DESKTOP
memcpy(sentence_cstr, get_by_id(it->gen_request_id)->generated.text, get_by_id(it->gen_request_id)->generated.text_length);
#endif
String8 sentence_str = S8CString(sentence_cstr);
// parse out from the sentence NPC action and dialog // parse out from the sentence NPC action and dialog
Action out = {0}; Action out = {0};
ArenaTemp scratch = GetScratch(0, 0);
Log("Parsing `%.*s`...\n", S8VArg(sentence_str)); Log("Parsing `%.*s`...\n", S8VArg(sentence_str));
String8 parse_response = parse_chatgpt_response(scratch.arena, it, sentence_str, &out); String8 parse_response = parse_chatgpt_response(frame_arena, it, sentence_str, &out);
// check that it wraps in below two lines // check that it wraps in below two lines
TextPlacementSettings *to_wrap_to = &speech_bubble; TextPlacementSettings *to_wrap_to = &speech_bubble;
@ -6369,8 +6438,6 @@ ISANERROR("Don't know how to do this stuff on this platform.")
{ {
Log("Performing action %s!\n", actions[out.kind].name); Log("Performing action %s!\n", actions[out.kind].name);
perform_action(&gs, it, out); perform_action(&gs, it, out);
} }
else else
{ {
@ -6379,42 +6446,19 @@ ISANERROR("Don't know how to do this stuff on this platform.")
} }
} }
ReleaseScratch(scratch);
#ifdef WEB
EM_ASM( {
done_with_generation_request($0);
}, it->gen_request_id);
#endif
#ifdef DESKTOP
done_with_request(it->gen_request_id); done_with_request(it->gen_request_id);
#endif it->gen_request_id = 0;
} }
else if (status == 2) break;
{ case GEN_Failed:
Log("Failed to generate dialog! Fuck!\n"); Log("Failed to generate dialog! Fuck!\n");
having_errors = true; having_errors = true;
it->gen_request_id = 0;
/* break;
Action to_perform = {0}; default:
String8 speech_mdstring = S8Lit("I'm not sure...");
memcpy(to_perform.speech, speech_mdstring.str, speech_mdstring.size);
to_perform.speech_length = (int)speech_mdstring.size;
perform_action(&gs, it, to_perform);
*/
}
else if (status == -1)
{
Log("Generation request doesn't exist anymore, that's fine...\n");
}
else
{
Log("Unknown generation request status: %d\n", status); Log("Unknown generation request status: %d\n", status);
}
it->gen_request_id = 0; it->gen_request_id = 0;
break;
} }
} }
} }
@ -6831,38 +6875,19 @@ ISANERROR("Don't know how to do this stuff on this platform.")
{ {
it->perceptions_dirty = false; // needs to be in beginning because they might be redirtied by the new perception it->perceptions_dirty = false; // needs to be in beginning because they might be redirtied by the new perception
String8 prompt_str = {0}; String8 prompt_str = {0};
#ifdef DO_CHATGPT_PARSING
prompt_str = generate_chatgpt_prompt(frame_arena, &gs, it, get_can_talk_to(it)); prompt_str = generate_chatgpt_prompt(frame_arena, &gs, it, get_can_talk_to(it));
#else Log("Want to make request with prompt `%.*s`\n", S8VArg(prompt_str));
generate_prompt(it, &prompt);
#endif
Log("Sending request with prompt `%.*s`\n", S8VArg(prompt_str));
#ifdef WEB
// fire off generation request, save id
ArenaTemp scratch = GetScratch(0, 0);
String8 terminated_completion_url = nullterm(scratch.arena, FmtWithLint(scratch.arena, "%s://%s:%d/completion", IS_SERVER_SECURE ? "https" : "http", SERVER_DOMAIN, SERVER_PORT));
int req_id = EM_ASM_INT({
return make_generation_request(UTF8ToString($0, $1), UTF8ToString($2, $3));
},
prompt_str.str, (int)prompt_str.size, terminated_completion_url.str, (int)terminated_completion_url.size);
it->gen_request_id = req_id;
ReleaseScratch(scratch);
#endif
#ifdef DESKTOP
ArenaTemp scratch = GetScratch(0, 0);
String8 ai_response = {0};
bool mocking_the_ai_response = false; bool mocking_the_ai_response = false;
#ifdef DEVTOOLS #ifdef DEVTOOLS
#ifdef MOCK_AI_RESPONSE #ifdef MOCK_AI_RESPONSE
mocking_the_ai_response = true; mocking_the_ai_response = true;
#endif #endif // mock
#endif #endif // devtools
bool succeeded = true; // couldn't get AI response if false bool succeeded = true; // couldn't get AI response if false
if (mocking_the_ai_response) if (mocking_the_ai_response)
{ {
String8 ai_response = {0};
if (it->memories_last->context.talking_to_kind == it->npc_kind) if (it->memories_last->context.talking_to_kind == it->npc_kind)
//if (it->memories_last->context.author_npc_kind != it->npc_kind) //if (it->memories_last->context.author_npc_kind != it->npc_kind)
{ {
@ -6899,14 +6924,13 @@ ISANERROR("Don't know how to do this stuff on this platform.")
} }
// something to mock // something to mock
if (ai_response.size > 0) assert(ai_response.size > 0);
{
Log("Mocking...\n"); Log("Mocking...\n");
Action a = {0}; Action a = {0};
String8 error_message = S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__)); String8 error_message = S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__));
if (succeeded) if (succeeded)
{ {
error_message = parse_chatgpt_response(scratch.arena, it, ai_response, &a); error_message = parse_chatgpt_response(frame_arena, it, ai_response, &a);
} }
assert(succeeded); assert(succeeded);
@ -6916,17 +6940,11 @@ ISANERROR("Don't know how to do this stuff on this platform.")
assert(valid_str.size == 0); assert(valid_str.size == 0);
perform_action(&gs, it, a); perform_action(&gs, it, a);
} }
}
else else
{ {
String8 post_request_body = FmtWithLint(scratch.arena, "|%.*s", S8VArg(prompt_str)); it->gen_request_id = make_generation_request(prompt_str);
it->gen_request_id = make_generation_request(post_request_body);
} }
ReleaseScratch(scratch);
#undef SAY #undef SAY
#endif // desktop endif
} }
} }
} }
@ -7203,7 +7221,6 @@ ISANERROR("Don't know how to do this stuff on this platform.")
#define HELPER_SIZE 250.0f #define HELPER_SIZE 250.0f
// keyboard tutorial icons // keyboard tutorial icons
if(false) if(false)
if (!mobile_controls) if (!mobile_controls)
@ -7236,6 +7253,11 @@ ISANERROR("Don't know how to do this stuff on this platform.")
#ifdef DEVTOOLS #ifdef DEVTOOLS
if(keypressed[SAPP_KEYCODE_J])
{
Log("Judgement Day!\n");
}
// statistics @Place(devtools drawing developer menu drawing) // statistics @Place(devtools drawing developer menu drawing)
if (show_devtools) if (show_devtools)
PROFILE_SCOPE("devtools drawing") PROFILE_SCOPE("devtools drawing")

@ -298,6 +298,7 @@ typedef BUFF(ActionKind, 8) AvailableActions;
typedef struct GameState { typedef struct GameState {
Arena *arena;
uint64_t tick; uint64_t tick;
bool won; bool won;
@ -306,6 +307,10 @@ typedef struct GameState {
bool assigned_objective; bool assigned_objective;
GameplayObjective objective; GameplayObjective objective;
Memory *judgement_memories;
double time; // in seconds, fraction of length of day
int judgement_gen_request;
// processing may still occur after time has stopped on the gamestate, // processing may still occur after time has stopped on the gamestate,
bool stopped_time; bool stopped_time;

@ -27,6 +27,7 @@ Urgent:
Long distance: Long distance:
- nocodegen instead of codegen argument - nocodegen instead of codegen argument
- Blur game on bitmap modal input mode - Blur game on bitmap modal input mode
- Detect when an arena accidentally has more than one ArenaTemp requested of it
- Polygon and circle collision with cutec2 probably for the player being unable to collide with the camera bounds, and non axis aligned collision rects - Polygon and circle collision with cutec2 probably for the player being unable to collide with the camera bounds, and non axis aligned collision rects
- set the game in oregon (suggestion by phillip) - set the game in oregon (suggestion by phillip)
- Room system where characters can go to rooms. camera constrained to room bounds, and know which rooms are near them to go to - Room system where characters can go to rooms. camera constrained to room bounds, and know which rooms are near them to go to

@ -3,6 +3,7 @@
#define RANDOM_SEED 42 #define RANDOM_SEED 42
#define LEVEL_TILES 150 // width and height of level tiles array #define LEVEL_TILES 150 // width and height of level tiles array
#define LENGTH_OF_DAY (120.0) // double in seconds
#define LAYERS 3 #define LAYERS 3
#define TILE_SIZE 0.5f // in pixels #define TILE_SIZE 0.5f // in pixels
#define PLAYER_SPEED 0.15f // in meters per second #define PLAYER_SPEED 0.15f // in meters per second

Loading…
Cancel
Save