Skip to content

Commit f901889

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 f901889

24 files changed

Lines changed: 477 additions & 419 deletions

code/hud/hudmessage.cpp

Lines changed: 43 additions & 69 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,43 @@ 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) {
816+
if (!Msg_scrollback_vec.empty() && HUD_msg_inited) {
836817

837-
for (int j = 0; j < (int)Msg_scrollback_vec.size(); j++) {
838-
line_node node_msg = Msg_scrollback_vec[j];
818+
for (auto &node_msg: Msg_scrollback_vec) {
819+
auto text = node_msg.text.c_str();
820+
int max_width = Hud_mission_log_list2_coords[gr_screen.res][2];
839821

840822
int width = 0;
841823
int height = 0;
842-
gr_get_string_size(&width, &height, node_msg.text.c_str(), 1.0f, node_msg.text.length());
843-
844-
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;
862-
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-
}
824+
bool first = true;
825+
size_t start_pos = 0;
826+
size_t split_len, split_next_pos;
827+
828+
do {
829+
std::tie(split_len, split_next_pos, std::ignore) = split_str_once(text, max_width, std::string::npos, 1.0f, &width, &height);
830+
831+
Msg_scrollback_lines.emplace_back(
832+
first ? node_msg.time : 0,
833+
first ? node_msg.timer_padding : 0,
834+
node_msg.source,
835+
node_msg.x,
836+
split_next_pos > 0 ? 1 : height / 3, // y coordinate is 1 for every line except the last one
837+
first ? node_msg.underline_width : 0,
838+
node_msg.text.substr(start_pos, split_len)
839+
);
840+
841+
text += split_next_pos;
842+
start_pos += split_next_pos;
843+
first = false;
844+
} while (split_next_pos > 0);
869845
}
870846
}
871847
}
872848

873849
void hud_scrollback_init()
874850
{
875-
876851
// pause all game sounds
877852
weapon_pause_sounds();
878853
audiostream_pause_all();
@@ -1050,8 +1025,7 @@ void hud_scrollback_do_frame(float /*frametime*/)
10501025
int y = 0;
10511026
if (!Msg_scrollback_lines.empty() && HUD_msg_inited) {
10521027
int i = 0;
1053-
for (int j = 0; j < (int)Msg_scrollback_lines.size(); j++) {
1054-
line_node node_msg = Msg_scrollback_lines[j];
1028+
for (auto &node_msg: Msg_scrollback_lines) {
10551029
if ((node_msg.source == HUD_SOURCE_HIDDEN) || (i++ < Scroll_offset)) {
10561030
continue;
10571031

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: 33 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -439,12 +439,7 @@ int mission_log_get_count( LogType type, const char *pname, const char *sname )
439439

440440
void message_log_add_seg(log_text_seg *entry, int x, int line_offset, int msg_color, const char *text, int flags = 0)
441441
{
442-
// set the vector
443-
entry->text.reset(vm_strdup(text));
444-
entry->color = msg_color;
445-
entry->x = x;
446-
entry->line_offset = line_offset;
447-
entry->flags = flags;
442+
message_log_add_seg(entry, x, line_offset, msg_color, text, strlen(text), flags);
448443
}
449444

450445
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)
@@ -457,66 +452,59 @@ void message_log_add_seg(log_text_seg *entry, int x, int line_offset, int msg_co
457452
entry->flags = 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

code/mission/missiontraining.cpp

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,6 @@ void HudGaugeDirectives::render(float /*frametime*/, bool config)
282282
int bx = x;
283283
int by = y + fl2i(middle_frame_offset_y * scale);
284284

285-
char* second_line;
286285
for (int i=0; i<end; i++) {
287286
int ox = x + fl2i(text_start_offsets[0] * scale);
288287
int oy = y + fl2i(text_start_offsets[1] * scale) + Training_obj_num_display_lines * fl2i(text_h * scale);
@@ -291,36 +290,37 @@ void HudGaugeDirectives::render(float /*frametime*/, bool config)
291290
int line_x_offset = 0;
292291

293292
color* c = &Color_normal;
294-
char buf[256];
293+
SCP_string buf;
295294
if (!config && Training_obj_lines[i + offset] & TRAINING_OBJ_LINES_KEY) {
296-
SCP_string temp_buf = message_translate_tokens(Mission_events[z].objective_key_text.c_str()); // remap keys
297-
strcpy_s(buf, temp_buf.c_str());
295+
buf = message_translate_tokens(Mission_events[z].objective_key_text.c_str()); // remap keys
298296
c = &Color_bright_green;
299297
line_x_offset = fl2i(key_line_x_offset * scale);
300298
} else if (config){
301299
switch (i) {
302300
case 0:
303-
strcpy_s(buf, "Destroy Enemies");
301+
buf = "Destroy Enemies";
304302
c = &Color_bright_white;
305303
break;
306304
case 1:
307-
strcpy_s(buf, "Target hostile fighters");
305+
buf = "Target hostile fighters";
308306
c = &Color_bright_green;
309307
line_x_offset = fl2i(key_line_x_offset * scale);
310308
break;
311309
case 2:
312-
strcpy_s(buf, "Protect Friendlies");
310+
buf = "Protect Friendlies";
313311
c = &Color_bright_red;
314312
break;
315313
case 3:
316-
strcpy_s(buf, "Escort Cruiser");
314+
buf = "Escort Cruiser";
317315
c = &Color_bright_blue;
318316
break;
319317
}
320318
} else {
321-
strcpy_s(buf, Mission_events[z].objective_text.c_str());
319+
buf = Mission_events[z].objective_text;
322320
if (Mission_events[z].count){
323-
sprintf(buf + strlen(buf), NOX(" [%d]"), Mission_events[z].count);
321+
buf += NOX(" [");
322+
buf += std::to_string(Mission_events[z].count);
323+
buf += NOX("]");
324324
}
325325

326326
// if this is a multiplayer tvt game, and this is event is not for my team, don't display it
@@ -361,12 +361,8 @@ void HudGaugeDirectives::render(float /*frametime*/, bool config)
361361
}
362362

363363
// maybe split the directives line
364-
second_line = split_str_once(buf, fl2i(max_line_width * scale), scale);
365-
366-
// if we are unable to split the line, just print it once
367-
if (second_line == buf) {
368-
second_line = nullptr;
369-
}
364+
size_t split_len, split_next_pos;
365+
std::tie(split_len, split_next_pos, std::ignore) = split_str_once(buf.c_str(), fl2i(max_line_width * scale), std::string::npos, scale);
370366

371367
// blit the background frames
372368
setGaugeColor(HUD_C_NONE, config);
@@ -376,7 +372,7 @@ void HudGaugeDirectives::render(float /*frametime*/, bool config)
376372

377373
by += fl2i(text_h * scale);
378374

379-
if ( second_line ) {
375+
if ( split_next_pos > 0 ) {
380376

381377
if (directives_middle.first_frame >= 0)
382378
renderBitmap(directives_middle.first_frame, bx, by, scale, config);
@@ -387,14 +383,17 @@ void HudGaugeDirectives::render(float /*frametime*/, bool config)
387383
// blit the text
388384
gr_set_color_fast(c);
389385

390-
renderString(ox + line_x_offset, oy, EG_OBJ1 + i, buf, scale, config);
386+
renderString(ox + line_x_offset, oy, EG_OBJ1 + i, buf.c_str(), split_len, scale, config);
391387

392388
Training_obj_num_display_lines++;
393389

394-
if ( second_line ) {
390+
if ( split_next_pos > 0 ) {
391+
auto second_line = buf.c_str() + split_next_pos;
392+
std::tie(split_len, split_next_pos, std::ignore) = split_str_once(second_line, fl2i(max_line_width * scale), std::string::npos, scale);
393+
395394
oy = y + fl2i(text_start_offsets[1] * scale) + Training_obj_num_display_lines * fl2i(text_h * scale);
396395

397-
renderString(ox+12, oy, EG_OBJ1 + i + 1, second_line, scale, config);
396+
renderString(ox+12, oy, EG_OBJ1 + i + 1, second_line, split_len, scale, config);
398397

399398
Training_obj_num_display_lines++;
400399
}

0 commit comments

Comments
 (0)