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

258
main.c

@ -460,6 +460,14 @@ LPCWSTR windows_string(String8 s)
}
#endif
typedef enum
{
GEN_Deleted = -1,
GEN_NotDoneYet = 0,
GEN_Success = 1,
GEN_Failed = 2,
} GenRequestStatus;
#ifdef DESKTOP
#ifdef WINDOWS
#pragma warning(push, 3)
@ -549,7 +557,7 @@ void generation_thread(void* my_request_voidptr)
WinAssertWithErrorCode(WinHttpReadData(hRequest, (LPVOID)out_buffer, dwSize, &dwDownloaded));
out_buffer[dwDownloaded - 1] = '\0';
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);
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
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);
ReleaseScratch(scratch);
return to_return->id;
}
@ -625,12 +636,68 @@ void done_with_request(int id)
DblRemove(requests_first, requests_last, 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
ISANERROR("Only know how to do desktop http requests on windows")
#endif // WINDOWS
#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;
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};
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);
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;
}
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;
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)
{
if(gs->arena)
{
ArenaRelease(gs->arena);
}
memset(gs, 0, sizeof(GameState));
gs->arena = ArenaAlloc();
rnd_gamerand_seed(&gs->random, RANDOM_SEED);
// 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("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("J - judges the player and outputs their verdict to the console\n");
#undef P
String8 to_return = S8ListJoin(arena, list, &(StringJoin){0});
@ -5591,7 +5679,7 @@ TextPlacementSettings speech_bubble = {
String8List words_on_current_page(Entity *it, TextPlacementSettings *settings)
{
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};
for(PlacedWord *cur = placed.first; cur; cur = cur->next)
@ -5669,6 +5757,8 @@ void frame(void)
{
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);
Vec3 player_pos = V3(gs.player->pos.x, 0.0, gs.player->pos.y);
@ -5713,7 +5803,7 @@ void frame(void)
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
light_dir = NormV3(MulV3F(sun_vector, -1.0f));
}
@ -6304,61 +6394,40 @@ void frame(void)
{
assert(it->gen_request_id > 0);
#ifdef DESKTOP
int status = get_by_id(it->gen_request_id)->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
GenRequestStatus status = gen_request_status(it->gen_request_id);
switch(status)
{
if (status == 1)
case GEN_Deleted:
it->gen_request_id = 0;
break;
case GEN_NotDoneYet:
break;
case GEN_Success:
{
having_errors = false;
// done! we can get the string
char sentence_cstr[MAX_SENTENCE_LENGTH] = { 0 };
#ifdef WEB
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);
TextChunk sentence_chunk = gen_request_content(it->gen_request_id);
String8 sentence_str = TextChunkString8(sentence_chunk);
// parse out from the sentence NPC action and dialog
Action out = {0};
ArenaTemp scratch = GetScratch(0, 0);
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
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);
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;
}
}
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);
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);
perform_action(&gs, it, out);
}
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);
#endif
}
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);
it->gen_request_id = 0;
}
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;
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
String8 prompt_str = {0};
#ifdef DO_CHATGPT_PARSING
prompt_str = generate_chatgpt_prompt(frame_arena, &gs, it, get_can_talk_to(it));
#else
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
Log("Want to make request with prompt `%.*s`\n", S8VArg(prompt_str));
#ifdef DESKTOP
ArenaTemp scratch = GetScratch(0, 0);
String8 ai_response = {0};
bool mocking_the_ai_response = false;
#ifdef DEVTOOLS
#ifdef MOCK_AI_RESPONSE
mocking_the_ai_response = true;
#endif
#endif
#endif // mock
#endif // devtools
bool succeeded = true; // couldn't get AI response if false
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.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
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");
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);
}
error_message = parse_chatgpt_response(frame_arena, it, ai_response, &a);
}
assert(succeeded);
assert(error_message.size == 0);
assert(succeeded);
assert(error_message.size == 0);
String8 valid_str = is_action_valid(frame_arena, it, a);
assert(valid_str.size == 0);
perform_action(&gs, it, a);
}
String8 valid_str = is_action_valid(frame_arena, it, a);
assert(valid_str.size == 0);
perform_action(&gs, it, a);
}
else
{
String8 post_request_body = FmtWithLint(scratch.arena, "|%.*s", S8VArg(prompt_str));
it->gen_request_id = make_generation_request(post_request_body);
it->gen_request_id = make_generation_request(prompt_str);
}
ReleaseScratch(scratch);
#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
// keyboard tutorial icons
if(false)
if (!mobile_controls)
@ -7236,6 +7253,11 @@ ISANERROR("Don't know how to do this stuff on this platform.")
#ifdef DEVTOOLS
if(keypressed[SAPP_KEYCODE_J])
{
Log("Judgement Day!\n");
}
// statistics @Place(devtools drawing developer menu drawing)
if (show_devtools)
PROFILE_SCOPE("devtools drawing")

@ -298,6 +298,7 @@ typedef BUFF(ActionKind, 8) AvailableActions;
typedef struct GameState {
Arena *arena;
uint64_t tick;
bool won;
@ -306,6 +307,10 @@ typedef struct GameState {
bool assigned_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,
bool stopped_time;

@ -27,6 +27,7 @@ Urgent:
Long distance:
- nocodegen instead of codegen argument
- 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
- 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

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

Loading…
Cancel
Save