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
Cameron Murphy Reikes 9 months ago
parent fc70f8a043
commit 6225eb542f

258
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,61 +6394,40 @@ 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;
PlacedWordList placed = place_wrapped_words(frame_arena, split_by_word(frame_arena, TextChunkString8(out.speech)), to_wrap_to->text_scale, to_wrap_to->text_width_in_pixels, *to_wrap_to->font, JUST_LEFT); PlacedWordList placed = place_wrapped_words(frame_arena, split_by_word(frame_arena, TextChunkString8(out.speech)), to_wrap_to->text_scale, to_wrap_to->text_width_in_pixels, *to_wrap_to->font, JUST_LEFT);
int words_over_limit = 0; int words_over_limit = 0;
for(PlacedWord *cur = placed.first; cur; cur = cur->next) for (PlacedWord *cur = placed.first; cur; cur = cur->next)
{ {
if(cur->line_index >= to_wrap_to->lines_per_page*to_wrap_to->maximum_pages_from_ai) // the max number of lines of text on a bubble if (cur->line_index >= to_wrap_to->lines_per_page * to_wrap_to->maximum_pages_from_ai) // the max number of lines of text on a bubble
{ {
words_over_limit += 1; words_over_limit += 1;
} }
} }
if(words_over_limit > 0) if (words_over_limit > 0)
{ {
String8 new_err = FmtWithLint(frame_arena, "Your speech is %d words over the maximum limit, you must be more succinct and remove at least that many words", words_over_limit); String8 new_err = FmtWithLint(frame_arena, "Your speech is %d words over the maximum limit, you must be more succinct and remove at least that many words", words_over_limit);
append_to_errors(it, make_memory(out, (MemoryContext){.i_said_this = true, .author_npc_kind = it->npc_kind, .talking_to_kind = out.talking_to_kind}), new_err); append_to_errors(it, make_memory(out, (MemoryContext){.i_said_this = true, .author_npc_kind = it->npc_kind, .talking_to_kind = out.talking_to_kind}), new_err);
@ -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)
{
Log("Failed to generate dialog! Fuck!\n");
having_errors = true;
/*
Action to_perform = {0};
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);
} }
break;
case GEN_Failed:
Log("Failed to generate dialog! Fuck!\n");
having_errors = true;
it->gen_request_id = 0;
break;
default:
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,34 +6924,27 @@ 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");
Action a = {0};
String8 error_message = S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__));
if (succeeded)
{ {
Log("Mocking...\n"); error_message = parse_chatgpt_response(frame_arena, it, ai_response, &a);
Action a = {0}; }
String8 error_message = S8Lit("Something really bad happened bro. File " STRINGIZE(__FILE__) " Line " STRINGIZE(__LINE__));
if (succeeded)
{
error_message = parse_chatgpt_response(scratch.arena, it, ai_response, &a);
}
assert(succeeded); assert(succeeded);
assert(error_message.size == 0); assert(error_message.size == 0);
String8 valid_str = is_action_valid(frame_arena, it, a); String8 valid_str = is_action_valid(frame_arena, it, a);
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