Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/C_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,10 @@ Receives real-time text events without polling. Event types:
|------|---------|
| 0 | Text output (insertion) |
| 1 | Text delete (backspace) |
| 2 | Buffer cleared wholesale — `dasher_reset`, `dasher_reset_output_text`, or an alphabet change (which clears the buffer). `text` is empty; shadow buffers must be cleared, not diffed |

The callback fires on the thread calling `dasher_frame()`.
The callback fires on the thread calling `dasher_frame()`. Event type 2 may
also fire on the thread calling the reset function itself.

### Message Callback

Expand Down Expand Up @@ -421,7 +423,7 @@ int dasher_get_speed_percent(dasher_ctx* ctx);
void dasher_set_speed_percent(dasher_ctx* ctx, int percent);
```

- Range: 20–400 (clamped). Default: 100.
- Range: the engine's declared `LP_MAX_BITRATE` bounds (raw 1–1000 → ~1–625 %, in whole-percent steps of the raw unit). The previous fixed 20–400 clamp truncated Dasher v5's top speeds (v5 allowed raw 10–800 = up to 500 %); values are now clamped to the manifest range instead. Default: 100.
- Internally maps to `LP_MAX_BITRATE`: `bitrate = percent / 100.0 * 160`

## Parameters
Expand Down Expand Up @@ -655,7 +657,7 @@ dasher_frame(ctx, System.currentTimeMillis(), cmds, cmdCount, null, null)
2. **`out_command_count` is total int count, not command count** — divide by 6 for command count.
3. **String pointers are ephemeral** — copy immediately if you need the value beyond the current API call.
4. **Localization state is global** — changing locale in one context affects all contexts (shared static state).
5. **Speed percent mapping** — 100% = `LP_MAX_BITRATE` of 160, range 20–400%.
5. **Speed percent mapping** — 100% = `LP_MAX_BITRATE` of 160, clamped to the engine's declared `LP_MAX_BITRATE` range (not a fixed percent cap).
6. **Language model ID gap** — IDs are 0, 2, 3, 4 (no ID 1). Historical.
7. **No font rendering** — text width is estimated as `characters * fontSize / 2`. Actual font metrics are the frontend's responsibility.
8. **Polygons are decomposed into line segments** — no filled polygon opcode.
Expand Down
28 changes: 25 additions & 3 deletions src/CAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -945,11 +945,20 @@ DASHER_API const char* dasher_get_output_text(dasher_ctx* ctx) {
return ctx->tlString.c_str();
}

// Notify subscribers that the edit buffer was cleared wholesale (event type
// 2). Insert/delete deltas (0/1) can't express this, and without the event
// every frontend has to know which API calls clear the buffer and re-sync
// manually — Dasher-GTK's stale output pane after "New" was exactly this bug.
static void notify_buffer_cleared(dasher_ctx* ctx) {
if (ctx->outputCb) ctx->outputCb(2, "", ctx->outputCbUserData);
}

DASHER_API void dasher_reset_output_text(dasher_ctx* ctx) {
if (!ctx) return;
ctx->editBuffer.clear();
ctx->cursorPos = 0;
ctx->rateTimestamps.clear();
notify_buffer_cleared(ctx);
}

DASHER_API void dasher_reset(dasher_ctx* ctx) {
Expand All @@ -958,6 +967,7 @@ DASHER_API void dasher_reset(dasher_ctx* ctx) {
ctx->cursorPos = 0;
ctx->rateTimestamps.clear();
ctx->intf->SetOffset(0, true);
notify_buffer_cleared(ctx);
}

DASHER_API const char* dasher_get_alphabet_id(dasher_ctx* ctx) {
Expand All @@ -970,6 +980,7 @@ DASHER_API void dasher_set_alphabet_id(dasher_ctx* ctx, const char* alphabet_id)
if (!ctx || !ctx->intf || !alphabet_id) return;
ctx->editBuffer.clear();
ctx->cursorPos = 0;
notify_buffer_cleared(ctx); // documented side effect: setting clears the buffer
if (!ctx->realized) {
ctx->pendingAlphabet = alphabet_id;
return;
Expand Down Expand Up @@ -1049,9 +1060,20 @@ DASHER_API void dasher_set_speed_percent(dasher_ctx* ctx, int percent) {
if (!ctx || !ctx->intf) return;
try {
const double base = 160.0;
const int clamped = (percent < 20) ? 20 : (percent > 400) ? 400 : percent;
long bitrate = static_cast<long>(lround_int(clamped / 100.0 * base));
if (bitrate < 1) bitrate = 1;
// Clamp to the engine's declared LP_MAX_BITRATE range rather than the
// historic 20–400 %: that cap was raw 32–640, which silently truncated
// the top of Dasher v5's speed range (v5 allowed raw 10–800, i.e. up
// to 500 %). Frontend speed controls should take their bounds from the
// same manifest (dasher_get_parameter_info).
long min_bitrate = 1, max_bitrate = 1000;
auto it = Dasher::Settings::parameter_defaults.find(Dasher::LP_MAX_BITRATE);
if (it != Dasher::Settings::parameter_defaults.end() && it->second.max > 0) {
min_bitrate = it->second.min;
max_bitrate = it->second.max;
}
long bitrate = static_cast<long>(lround_int(percent / 100.0 * base));
if (bitrate < min_bitrate) bitrate = min_bitrate;
if (bitrate > max_bitrate) bitrate = max_bitrate;
ctx->intf->SetLongParameter(Dasher::LP_MAX_BITRATE, bitrate);
} catch (const std::exception& e) {
log_boundary_error(ctx, "dasher_set_speed_percent", e.what());
Expand Down
16 changes: 13 additions & 3 deletions src/dasher.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,11 @@ DASHER_API int dasher_get_language_model_param_key(int id, int index);
// Returns -1 if not found.
DASHER_API int dasher_find_parameter_key(const char* enum_key_name);

// Get/set speed as a percentage (100 = default, range 20-400).
// Get/set speed as a percentage (100 = raw LP_MAX_BITRATE 160). The set
// clamps to the engine's declared LP_MAX_BITRATE range (see
// dasher_get_parameter_info), not a fixed percent cap — Dasher v5 allowed raw
// 10–800 (6–500 %), and the historic 20–400 percent clamp silently truncated
// the top of that range.
DASHER_API int dasher_get_speed_percent(dasher_ctx* ctx);
DASHER_API void dasher_set_speed_percent(dasher_ctx* ctx, int percent);

Expand Down Expand Up @@ -341,8 +345,14 @@ DASHER_API void dasher_reset_settings(dasher_ctx* ctx);
// Event types:
// 0 = text output — text is the string being inserted
// 1 = text delete — text is the string being removed (backspace)
//
// The callback fires on the thread that calls dasher_frame().
// 2 = buffer clear — text is empty; the whole buffer was discarded
// (dasher_reset, dasher_reset_output_text, or an
// alphabet change). Deltas alone cannot express this,
// so subscribers maintaining a shadow buffer must
// treat this as "clear your copy".
//
// The callback fires on the thread that calls dasher_frame(). Event type 2
// may also fire from the thread calling the reset function itself.

typedef void (*dasher_output_callback)(int event_type, const char* text, void* user_data);

Expand Down
13 changes: 13 additions & 0 deletions tests/test_capi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,19 @@ TEST(parameters) {
speed = dasher_get_speed_percent(ctx);
ASSERT_EQ(speed, 150);

// The percent setter clamps to the engine's declared LP_MAX_BITRATE range
// (raw 1–1000 → ~1–625 %), not the historic fixed 20–400 cap: Dasher v5
// allowed raw 10–800 (up to 500 %) and power users rely on the top of it.
dasher_set_speed_percent(ctx, 500); // v5's max, raw 800
ASSERT_EQ(dasher_get_speed_percent(ctx), 500);
dasher_set_speed_percent(ctx, 100000); // clamped to raw 1000 → 625 %
ASSERT_EQ(dasher_get_speed_percent(ctx), 625);
dasher_set_speed_percent(ctx, 0); // clamped up to raw 1 → 1 %
ASSERT_EQ(dasher_get_speed_percent(ctx), 1);
// Restore a mid value for the rest of the suite.
dasher_set_speed_percent(ctx, 150);
ASSERT_EQ(dasher_get_speed_percent(ctx), 150);

int model = dasher_get_language_model_id(ctx);
CHECK(model >= 0);

Expand Down
47 changes: 47 additions & 0 deletions tests/test_capi_extended.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,53 @@ TEST(reset) {
dasher_destroy(ctx);
}

TEST(reset_emits_buffer_clear_event) {
// Resets clear the edit buffer without insert/delete deltas, so they must
// announce themselves as event type 2 — subscribers keeping a shadow
// buffer cannot reconstruct "everything vanished" from 0/1 events
// (Dasher-GTK's stale output pane after "New" was this bug).
dasher_ctx* ctx = create_isolated_context();
ASSERT(ctx != nullptr);
dasher_set_screen_size(ctx, 800, 600);

static int clear_events = 0;
static int other_events = 0;
clear_events = 0;
other_events = 0;

dasher_set_output_callback(
ctx,
[](int event_type, const char* text, void*) {
if (event_type == 2) {
ASSERT(text != nullptr); // empty string, never null
clear_events++;
} else if (event_type == 0 || event_type == 1) {
other_events++;
}
// Unknown future event types must be ignored, not fatal.
},
nullptr);

dasher_reset_output_text(ctx);
ASSERT_EQ(clear_events, 1);

dasher_reset(ctx);
ASSERT_EQ(clear_events, 2);

// Alphabet changes clear the buffer as a documented side effect, so they
// fire the event too.
dasher_set_alphabet_id(ctx, "English with numerals and limited punctuation");
ASSERT_EQ(clear_events, 3);

// Setting the *same* alphabet still cleared the buffer (historic
// behaviour), so the event fires regardless.
dasher_set_alphabet_id(ctx, "English with numerals and limited punctuation");
ASSERT_EQ(clear_events, 4);

(void)other_events;
dasher_destroy(ctx);
}

TEST(save_settings) {
static int save_test_counter = 0;
char shared_dir[256];
Expand Down
12 changes: 9 additions & 3 deletions tests/test_parameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,23 @@ TEST(param_speed_clamping) {
ASSERT(ctx != nullptr);
dasher_set_screen_size(ctx, 800, 600);

// The percent setter clamps to the engine's declared LP_MAX_BITRATE range
// (raw 1–1000 → ~1–625 %), replacing the historic fixed 20–400 cap that
// truncated Dasher v5's top speeds (v5 allowed raw 10–800 = up to 500 %).
dasher_set_speed_percent(ctx, 50);
int speed = dasher_get_speed_percent(ctx);
ASSERT(speed >= 20 && speed <= 400);
ASSERT(speed >= 1 && speed <= 625);

dasher_set_speed_percent(ctx, 1000);
speed = dasher_get_speed_percent(ctx);
ASSERT(speed <= 400);
ASSERT(speed <= 625); // raw clamped to 1000 → 625 %

dasher_set_speed_percent(ctx, 1);
speed = dasher_get_speed_percent(ctx);
ASSERT(speed >= 20);
ASSERT(speed >= 1); // raw clamped up to the engine minimum

dasher_set_speed_percent(ctx, 500); // v5's maximum, must survive exactly
ASSERT(dasher_get_speed_percent(ctx) == 500);

dasher_destroy(ctx);
}
Expand Down
Loading