|
| 1 | +# 13. Native string manipulation vs `Raylib.text_format` |
| 2 | + |
| 3 | +Date: 2023-10-25 |
| 4 | + |
| 5 | +## Status |
| 6 | + |
| 7 | +Accepted |
| 8 | + |
| 9 | +## Context |
| 10 | + |
| 11 | +Our project involves frequent use of string formatting for display purposes. We were initially using Raylib's |
| 12 | +`TextFormat`/`Raylib.text_format` C function, bound through FFI, for this task. However, as our project primarily relies |
| 13 | +on Ruby, we have to decide whether to continue using Raylib.text_format or switch to native Ruby string manipulation |
| 14 | +methods, like string interpolation, `sprintf`, and `String#%`. |
| 15 | + |
| 16 | +Here's the C implementation of `TextFormat` from Raylib which our Ruby `binding Raylib.text_format` was based on: |
| 17 | + |
| 18 | +```c |
| 19 | +// Formatting of text with variables to 'embed' |
| 20 | +// WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times |
| 21 | +const char *TextFormat(const char *text, ...) |
| 22 | +{ |
| 23 | +#ifndef MAX_TEXTFORMAT_BUFFERS |
| 24 | + #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting |
| 25 | +#endif |
| 26 | + |
| 27 | + // We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations |
| 28 | + static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 }; |
| 29 | + static int index = 0; |
| 30 | + |
| 31 | + char *currentBuffer = buffers[index]; |
| 32 | + memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using |
| 33 | + |
| 34 | + va_list args; |
| 35 | + va_start(args, text); |
| 36 | + int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); |
| 37 | + va_end(args); |
| 38 | + |
| 39 | + // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occured |
| 40 | + if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) |
| 41 | + { |
| 42 | + // Inserting "..." at the end of the string to mark as truncated |
| 43 | + char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" |
| 44 | + sprintf(truncBuffer, "..."); |
| 45 | + } |
| 46 | + |
| 47 | + index += 1; // Move to next buffer for next function call |
| 48 | + if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; |
| 49 | + |
| 50 | + return currentBuffer; |
| 51 | +} |
| 52 | +``` |
| 53 | +
|
| 54 | +__Considerations__: Consistency and Maintainability: Our project predominantly uses Ruby as the programming language. |
| 55 | +Utilizing native Ruby methods for string manipulation promotes codebase consistency, making it more straightforward for |
| 56 | +developers to maintain and extend the project. |
| 57 | +
|
| 58 | +__Platform Independence__: Native Ruby string manipulation is part of the Ruby standard library and ensures |
| 59 | +cross-platform consistency, eliminating dependency on Raylib's C libraries for this specific feature. |
| 60 | +
|
| 61 | +__Ruby Community and Ecosystem__: Using native Ruby methods for string formatting allows us to tap into the |
| 62 | +extensive Ruby community and ecosystem for guidance and best practices, thereby enriching our codebase. |
| 63 | +
|
| 64 | +__Performance__: While the difference may not be significant, using native Ruby methods can result in faster |
| 65 | +execution time as it avoids the overhead of FFI calls to C libraries. |
| 66 | +
|
| 67 | +__Ease of Use__: Ruby’s native string manipulation methods are expressive, readable, and align well with the |
| 68 | +language's philosophy. |
| 69 | +
|
| 70 | +## Decision |
| 71 | +
|
| 72 | +We will use native Ruby methods for string formatting. This approach not only aligns with our language choice but |
| 73 | +also promotes code readability and maintainability. |
| 74 | +
|
| 75 | +Here are real examples that demonstrate the replacement of `Raylib.text_format` with Ruby’s native string manipulation: |
| 76 | +
|
| 77 | +```diff |
| 78 | +- Raylib.text_format("%i", :int, rand_value) |
| 79 | ++ rand_value.to_s |
| 80 | +
|
| 81 | +- Raylib.text_format("GP1: %s", :string, Raylib.get_gamepad_name(0)) |
| 82 | ++ "GP1: #{Raylib.get_gamepad_name(0)}" |
| 83 | +
|
| 84 | +- Raylib.draw_text(Raylib.text_format('Text size: [%02.02f, %02.02f]', :float, text_size.x, :float, text_size.y) |
| 85 | ++ 'Text size: [%02.02f, %02.02f]' % [text_size.x, text_size.y] |
| 86 | +``` |
| 87 | + |
| 88 | +## Consequences |
| 89 | + |
| 90 | +### Negative |
| 91 | + |
| 92 | +- Developers accustomed to Raylib's C-style string formatting may require time to adapt to Ruby's native methods. |
| 93 | + |
| 94 | +### Positive |
| 95 | + |
| 96 | +- Enhanced code readability and maintainability. |
| 97 | +- Improved performance by eliminating FFI overhead. |
| 98 | +- Alignment with Ruby's best practices and programming philosophy. |
0 commit comments