Skip to content

Commit 56db112

Browse files
committed
refactor split_str_once
Rewrite `split_str_once` to be more robust, to handle Unicode, and to return length and position information rather than modifying the source string, taking the approach also used by `str_wrap_to_width`. In combination with the new length variants of common string utility functions, this allows strings to be split without copying them. In many cases, using the old design, strings were copied multiple times per frame in numerous places. Includes unit tests to handle numerous usual and unusual cases. All of the historical string splitting anomalies should now be fixed, including a bug in the retail algorithm where an exactly matching string followed by whitespace wasn't split properly. A followup PR will refactor `split_str`.
1 parent fddedb3 commit 56db112

10 files changed

Lines changed: 515 additions & 422 deletions

File tree

code/hud/hudmessage.cpp

Lines changed: 43 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ static int Hud_mission_log_time2_coords[GR_NUM_RESOLUTIONS][2] = {
104104
#define SHOW_OBJS_BUTTON 4
105105
#define ACCEPT_BUTTON 5
106106

107-
#define HUD_MSG_LENGTH_MAX 2048
108107
//#define HUD_MSG_MAX_PIXEL_W 439 // maximum number of pixels wide message display area is
109108
//#define HUD_MSG_MAX_PIXEL_W 619 // maximum number of pixels wide message display area is
110109

@@ -293,47 +292,30 @@ void HudGaugeMessages::pageIn()
293292

294293
void HudGaugeMessages::processMessageBuffer()
295294
{
296-
int x, offset = 0;
297-
size_t i;
298-
char *msg;
299-
char *split_str, *ptr;
300-
301-
for ( i = 0; i < HUD_msg_buffer.size(); i++ ) {
302-
msg = new char [HUD_msg_buffer[i].text.size()+1];
303-
strcpy(msg, HUD_msg_buffer[i].text.c_str());
304-
305-
ptr = strstr(msg, NOX(": "));
306-
if ( ptr ) {
307-
int sw;
308-
gr_get_string_size(&sw, nullptr, msg, 1.0f, (ptr + 2 - msg));
309-
offset = sw;
310-
}
311-
312-
x = 0;
313-
split_str = msg;
314-
315-
while ((ptr = split_str_once(split_str, Max_width - x - 7)) != nullptr) { // the 7 is a fudge hack
316-
// make sure that split went ok, if not then bail
317-
if (ptr == split_str) {
318-
break;
319-
}
320-
321-
addPending(split_str, HUD_msg_buffer[i].source, x);
322-
split_str = ptr;
295+
for (auto &hud_msg: HUD_msg_buffer)
296+
{
297+
auto msg = hud_msg.text.c_str();
298+
299+
int x = 0, offset = 0;
300+
auto ptr = strstr(msg, NOX(": "));
301+
if (ptr)
302+
gr_get_string_size(&offset, nullptr, msg, 1.0f, static_cast<size_t>(ptr + 2 - msg));
303+
304+
size_t split_len, split_next_pos;
305+
do {
306+
std::tie(split_len, split_next_pos, std::ignore) = split_str_once(msg, Max_width - x - 7); // the 7 is a fudge hack
307+
addPending(msg, split_len, hud_msg.source, x);
308+
msg += split_next_pos;
323309
x = offset;
324-
}
325-
326-
addPending(split_str, HUD_msg_buffer[i].source, x);
327-
328-
delete[] msg;
310+
} while (split_next_pos > 0);
329311
}
330312
}
331313

332-
void HudGaugeMessages::addPending(const char *text, int source, int x)
314+
void HudGaugeMessages::addPending(const char *text, size_t len, int source, int x)
333315
{
334316
Assert(text != nullptr);
335317

336-
pending_messages.emplace(text, source, x);
318+
pending_messages.emplace(SCP_string(text, len), source, x);
337319
}
338320

339321
void HudGaugeMessages::scrollMessages()
@@ -829,50 +811,42 @@ void hud_scrollback_button_pressed(int n)
829811
// scroll height of the scrollback UI.
830812
void hud_initialize_scrollback_lines()
831813
{
832-
833814
Msg_scrollback_lines.clear();
834815

835-
if ((Msg_scrollback_vec.size() > 0) && HUD_msg_inited) {
836-
837-
for (int j = 0; j < (int)Msg_scrollback_vec.size(); j++) {
838-
line_node node_msg = Msg_scrollback_vec[j];
839-
840-
int width = 0;
841-
int height = 0;
842-
gr_get_string_size(&width, &height, node_msg.text.c_str(), 1.0f, node_msg.text.length());
816+
if (!Msg_scrollback_vec.empty() && HUD_msg_inited) {
843817

818+
for (auto &node_msg: Msg_scrollback_vec) {
819+
auto text = node_msg.text.c_str();
844820
int max_width = Hud_mission_log_list2_coords[gr_screen.res][2];
845-
if (width > max_width) {
846-
char c_text[HUD_MSG_LENGTH_MAX];
847-
strcpy_s(c_text, node_msg.text.c_str());
848-
849-
char* text = c_text;
850-
851-
char* split = split_str_once(text, max_width);
852-
Msg_scrollback_lines.push_back({node_msg.time, The_mission.HUD_timer_padding, node_msg.source, node_msg.x, 1, node_msg.underline_width, text});
853-
854-
while (split != nullptr) {
855-
text = split;
856-
split = nullptr;
857-
split = split_str_once(text, max_width);
858-
859-
int offset = 1;
860-
if (split == nullptr)
861-
offset = height / 3;
862821

863-
Msg_scrollback_lines.push_back({0, 0, node_msg.source, node_msg.x, offset, 0, text});
864-
}
865-
} else {
866-
node_msg.y = height / 3;
867-
Msg_scrollback_lines.push_back(std::move(node_msg));
868-
}
822+
int height = 0;
823+
bool first = true;
824+
size_t start_pos = 0;
825+
size_t split_len, split_next_pos;
826+
827+
do {
828+
std::tie(split_len, split_next_pos, std::ignore) = split_str_once(text, max_width, std::string::npos, 1.0f, nullptr, &height);
829+
830+
Msg_scrollback_lines.emplace_back(
831+
first ? node_msg.time : 0,
832+
first ? node_msg.timer_padding : 0,
833+
node_msg.source,
834+
node_msg.x,
835+
split_next_pos > 0 ? 1 : height / 3, // y coordinate is 1 for every line except the last one
836+
first ? node_msg.underline_width : 0,
837+
node_msg.text.substr(start_pos, split_len)
838+
);
839+
840+
text += split_next_pos;
841+
start_pos += split_next_pos;
842+
first = false;
843+
} while (split_next_pos > 0);
869844
}
870845
}
871846
}
872847

873848
void hud_scrollback_init()
874849
{
875-
876850
// pause all game sounds
877851
weapon_pause_sounds();
878852
audiostream_pause_all();
@@ -1050,8 +1024,7 @@ void hud_scrollback_do_frame(float /*frametime*/)
10501024
int y = 0;
10511025
if (!Msg_scrollback_lines.empty() && HUD_msg_inited) {
10521026
int i = 0;
1053-
for (int j = 0; j < (int)Msg_scrollback_lines.size(); j++) {
1054-
line_node node_msg = Msg_scrollback_lines[j];
1027+
for (auto &node_msg: Msg_scrollback_lines) {
10551028
if ((node_msg.source == HUD_SOURCE_HIDDEN) || (i++ < Scroll_offset)) {
10561029
continue;
10571030

code/hud/hudmessage.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class HudGaugeMessages: public HudGauge // HUD_MESSAGE_LINES
120120

121121
void clearMessages();
122122
void processMessageBuffer();
123-
void addPending(const char *text, int source, int x = 0);
123+
void addPending(const char *text, size_t len, int source, int x = 0);
124124
void scrollMessages();
125125
void preprocess() override;
126126
void render(float frametime, bool config = false) override;

code/mission/missionlog.cpp

Lines changed: 36 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -437,86 +437,74 @@ int mission_log_get_count( LogType type, const char *pname, const char *sname )
437437
}
438438

439439

440-
void message_log_add_seg(log_text_seg *entry, int x, int line_offset, int msg_color, const char *text, int flags = 0)
440+
void message_log_add_seg(log_text_seg *entry, int x, int line_offset, int msg_color, const char *text, size_t len, int flags = 0)
441441
{
442442
// set the vector
443-
entry->text.reset(vm_strdup(text));
443+
entry->text.reset(vm_strndup(text, len));
444444
entry->color = msg_color;
445445
entry->x = x;
446446
entry->line_offset = line_offset;
447447
entry->flags = flags;
448448
}
449449

450-
void message_log_add_seg(log_text_seg *entry, int x, int line_offset, int msg_color, const char *text, size_t len, int flags = 0)
450+
void message_log_add_seg(log_text_seg *entry, int x, int line_offset, int msg_color, const char *text, int flags = 0)
451451
{
452-
// set the vector
453-
entry->text.reset(vm_strndup(text, len));
454-
entry->color = msg_color;
455-
entry->x = x;
456-
entry->line_offset = line_offset;
457-
entry->flags = flags;
452+
message_log_add_seg(entry, x, line_offset, msg_color, text, strlen(text), flags);
458453
}
459454

460-
void message_log_add_segs(const char *source_string, int msg_color, int flags, SCP_vector<log_text_seg> *entry, bool split_string)
455+
void message_log_add_segs(const char *text, int msg_color, int flags, SCP_vector<log_text_seg> *entry, bool split_string)
461456
{
462-
if (!source_string || !entry) {
457+
if (!text || !entry) {
463458
mprintf(("Why are you passing a NULL pointer to message_log_add_segs?\n"));
464459
return;
465460
}
466-
if (!*source_string) {
467-
return;
468-
}
469-
470-
// duplicate the string so that we can split it without modifying the source
471-
char *dup_string = vm_strdup(source_string);
472-
char *str = dup_string;
473-
char *split = NULL;
474461

475462
if (split_string) {
476463
int sanity_counter = 0;
477464

478465
while (true) {
479466
if (X == ACTION_X) {
480-
while (is_white_space(*str))
481-
str++;
467+
ignore_white_space(&text);
468+
}
469+
if (!*text) {
470+
return;
482471
}
483472

484-
if (P_width - X > 0) {
485-
split = split_str_once(str, P_width - X);
473+
int w;
474+
auto [split_len, split_next_pos, forced] = split_str_once(text, P_width - X, std::string::npos, 1.0f, &w);
486475

487-
// if we couldn't actually split the string, try again on a new line
488-
if (split == str) {
489-
sanity_counter++;
490-
if (sanity_counter > 5) {
491-
Error(LOCATION, "Too many attempts to wrap a mission log line! Get a coder!\nLine = %s", str);
492-
break;
493-
}
494-
} else {
495-
log_text_seg new_seg;
496-
message_log_add_seg(&new_seg, X, Line_offset, msg_color, str, flags);
497-
entry->push_back(std::move(new_seg));
498-
499-
if (!split) {
500-
int w;
501-
gr_get_string_size(&w, nullptr, str);
502-
X += w;
503-
break;
504-
}
476+
// we have more text to write (since we didn't return, above), so if we don't actually have enough room, try again on a new line
477+
if (split_len == 0 || forced) {
478+
split_next_pos = 0;
479+
sanity_counter++;
480+
if (sanity_counter > 5) {
481+
Error(LOCATION, "Too many attempts to wrap a mission log line! Get a coder!\nLine = %s", text);
482+
break;
483+
}
484+
} else {
485+
log_text_seg new_seg;
486+
message_log_add_seg(&new_seg, X, Line_offset, msg_color, text, split_len, flags);
487+
entry->push_back(std::move(new_seg));
488+
489+
if (split_next_pos == 0) {
490+
X += w;
491+
break;
505492
}
506493
}
507494

508495
X = ACTION_X;
509496
Line_offset++;
510-
str = split;
497+
text += split_next_pos;
511498
}
512499
} else {
500+
if (!*text) {
501+
return;
502+
}
503+
513504
log_text_seg new_seg;
514-
message_log_add_seg(&new_seg, X, Line_offset, msg_color, str, flags);
505+
message_log_add_seg(&new_seg, X, Line_offset, msg_color, text, flags);
515506
entry->push_back(std::move(new_seg));
516507
}
517-
518-
// free the buffer
519-
vm_free(dup_string);
520508
}
521509

522510
int mission_log_color_get_team(int msg_color)
@@ -691,8 +679,7 @@ void mission_log_init_scrollback(int pw, bool split_string)
691679
Assert(!(entry.index & CARGO_NO_DEPLETE));
692680

693681
message_log_add_segs(XSTR("Cargo revealed: ", 418), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string);
694-
strncpy(text, Cargo_names[entry.index], sizeof(text) - 1);
695-
message_log_add_segs(text, LOG_COLOR_BRIGHT, 0, &thisEntry.segments, split_string);
682+
message_log_add_segs(Cargo_names[entry.index], LOG_COLOR_BRIGHT, 0, &thisEntry.segments, split_string);
696683
break;
697684

698685
case LOG_CAP_SUBSYS_CARGO_REVEALED:
@@ -701,8 +688,7 @@ void mission_log_init_scrollback(int pw, bool split_string)
701688

702689
message_log_add_segs(entry.sname_display.c_str(), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string);
703690
message_log_add_segs(XSTR( " subsystem cargo revealed: ", 1488), LOG_COLOR_NORMAL, 0, &thisEntry.segments, split_string);
704-
strncpy(text, Cargo_names[entry.index], sizeof(text) - 1);
705-
message_log_add_segs(text, LOG_COLOR_BRIGHT, 0, &thisEntry.segments, split_string);
691+
message_log_add_segs(Cargo_names[entry.index], LOG_COLOR_BRIGHT, 0, &thisEntry.segments, split_string);
706692
break;
707693

708694

0 commit comments

Comments
 (0)