Make machine configuration authoritative and restore native checkpoints exactly

This commit is contained in:
2026-09-18 09:09:41 +08:00
parent 6a69511276
commit 4a568f1f4c
94 changed files with 10882 additions and 301 deletions

File diff suppressed because one or more lines are too long

View File

@@ -25,6 +25,10 @@ uint32_t sim_read_snapshot(uint8_t *out, uint32_t capacity);
uint32_t sim_read_event(uint8_t *out, uint32_t capacity);
int32_t sim_export_state(uint8_t *out, uint32_t capacity);
int32_t sim_import_state(const uint8_t *data, uint32_t len);
uint32_t sim_core_checkpoint_size(void);
const char *sim_checkpoint_build_id(void);
int32_t sim_core_checkpoint_export(uint8_t *output, uint32_t capacity);
int32_t sim_core_checkpoint_restore(const uint8_t *input, uint32_t length);
void sim_destroy(void);
#ifdef __cplusplus

View File

@@ -27,6 +27,9 @@ typedef struct sim_event_record {
} sim_event_record;
void sim_event_reset(void);
uint32_t sim_event_checkpoint_size(void);
int32_t sim_event_checkpoint_export(uint8_t *output, uint32_t capacity);
int32_t sim_event_checkpoint_restore(const uint8_t *input, uint32_t length);
int32_t sim_event_append(uint32_t seq, uint32_t tick, const char *kind,
const uint8_t *payload, uint32_t payload_len);
uint32_t sim_event_count(void);

View File

@@ -14,6 +14,10 @@ extern "C" {
#define SIM_HAL_COMPONENT_CAPACITY 64U
#define SIM_HAL_PARAM_CAPACITY 256U
uint32_t sim_hal_checkpoint_size(void);
int32_t sim_hal_checkpoint_export(uint8_t *output, uint32_t capacity);
int32_t sim_hal_checkpoint_restore(const uint8_t *input, uint32_t length);
enum sim_hal_result {
SIM_HAL_OK = 0,
SIM_HAL_ERR_ARGUMENT = -300,

View File

@@ -74,6 +74,9 @@ int sim_interpreter_set_coordinates(sim_interpreter *interpreter, const sim_coor
int sim_interpreter_sync_position(sim_interpreter *interpreter, const sim_posemath_pose *position);
int sim_interpreter_init(sim_interpreter *interpreter);
uint32_t sim_interpreter_checkpoint_size(void);
int32_t sim_interpreter_checkpoint_export(const sim_interpreter *interpreter, uint8_t *output, uint32_t capacity);
int32_t sim_interpreter_checkpoint_restore(sim_interpreter *interpreter, const uint8_t *input, uint32_t length);
int sim_interpreter_reset(sim_interpreter *interpreter);
int sim_interpreter_execute(sim_interpreter *interpreter, const char *block, uint32_t length);
uint32_t sim_interpreter_size(void);

View File

@@ -18,6 +18,9 @@ typedef struct sim_linuxcnc_tp_segment {
} sim_linuxcnc_tp_segment;
uint32_t sim_linuxcnc_tp_size(void);
uint32_t sim_linuxcnc_tp_checkpoint_size(void);
int32_t sim_linuxcnc_tp_checkpoint_export(const sim_linuxcnc_tp *planner, uint8_t *output, uint32_t capacity);
int32_t sim_linuxcnc_tp_checkpoint_restore(sim_linuxcnc_tp *planner, const uint8_t *input, uint32_t length);
uint32_t sim_linuxcnc_tp_queue_capacity(const sim_linuxcnc_tp *planner);
int32_t sim_linuxcnc_tp_init(sim_linuxcnc_tp *planner, double cycle_time,
double velocity_limit, double acceleration_limit);
@@ -27,6 +30,10 @@ int32_t sim_linuxcnc_tp_set_feed_override(sim_linuxcnc_tp *planner, double scale
int32_t sim_linuxcnc_tp_set_velocity_scale(sim_linuxcnc_tp *planner, double scale);
int32_t sim_linuxcnc_tp_set_jog_scale(sim_linuxcnc_tp *planner, double scale);
int32_t sim_linuxcnc_tp_set_axis_limit(sim_linuxcnc_tp *planner, int axis, double minimum, double maximum);
int32_t sim_linuxcnc_tp_set_axis_dynamics(sim_linuxcnc_tp *planner, int axis, double velocity, double acceleration);
int32_t sim_linuxcnc_tp_set_feed_limit(sim_linuxcnc_tp *planner, double maximum);
int32_t sim_linuxcnc_tp_set_scale_minimum(sim_linuxcnc_tp *planner, double minimum);
int32_t sim_linuxcnc_tp_set_blend(sim_linuxcnc_tp *planner, int enable, int fallback, int depth, int gap, double ramp, double kink);
int32_t sim_linuxcnc_tp_jog(sim_linuxcnc_tp *planner, int axis, int direction, double increment, uint32_t tag);
double sim_linuxcnc_tp_velocity_scale(const sim_linuxcnc_tp *planner);
double sim_linuxcnc_tp_velocity_limit(const sim_linuxcnc_tp *planner);

View File

@@ -83,6 +83,9 @@ typedef struct sim_program {
} sim_program;
uint32_t sim_program_size(void);
uint32_t sim_program_checkpoint_size(void);
int32_t sim_program_checkpoint_export(const sim_program *program, uint8_t *output, uint32_t capacity);
int32_t sim_program_checkpoint_restore(sim_program *program, const uint8_t *input, uint32_t length);
int32_t sim_program_requires_subroutines(const char *source, uint32_t length);
int32_t sim_program_init(sim_program *program);
int32_t sim_program_reset(sim_program *program);

View File

@@ -37,6 +37,9 @@ int32_t sim_protocol_resume(void);
int32_t sim_protocol_state_value(void);
uint32_t sim_protocol_last_seq(void);
uint32_t sim_protocol_tick(void);
uint32_t sim_protocol_checkpoint_size(void);
int32_t sim_protocol_checkpoint_export(uint8_t *output, uint32_t capacity);
int32_t sim_protocol_checkpoint_restore(const uint8_t *input, uint32_t length);
int32_t sim_protocol_set_tick(uint32_t simulation_tick);
#ifdef __cplusplus

View File

@@ -11,6 +11,23 @@ typedef struct {
static sim_state state;
#ifndef SIM_CHECKPOINT_BUILD_ID
#define SIM_CHECKPOINT_BUILD_ID "unversioned"
#endif
const char *sim_checkpoint_build_id(void) { return SIM_CHECKPOINT_BUILD_ID; }
uint32_t sim_core_checkpoint_size(void) { return sizeof(state); }
int32_t sim_core_checkpoint_export(uint8_t *output, uint32_t capacity) {
if (output == NULL || capacity != sizeof(state)) return SIM_ERR_INVALID_ARGUMENT;
memcpy(output, &state, sizeof(state)); return SIM_OK;
}
int32_t sim_core_checkpoint_restore(const uint8_t *input, uint32_t length) {
if (input == NULL || length != sizeof(state)) return SIM_ERR_INVALID_ARGUMENT;
const sim_state *saved = (const sim_state *)input;
if (saved->created > 1 || saved->loaded > 1 || saved->pending_event > 1) return SIM_ERR_INVALID_ARGUMENT;
memcpy(&state, saved, sizeof(state)); return SIM_OK;
}
static int valid_buffer(const uint8_t *data, uint32_t len) {
return len == 0 || data != NULL;
}

View File

@@ -12,6 +12,14 @@ typedef struct sim_event_state {
static sim_event_state state;
uint32_t sim_event_checkpoint_size(void) { return sizeof(state); }
int32_t sim_event_checkpoint_export(uint8_t *output, uint32_t capacity) {
return sim_event_export(output, capacity) == sizeof(state) ? SIM_EVENT_OK : SIM_EVENT_ERR_BUFFER;
}
int32_t sim_event_checkpoint_restore(const uint8_t *input, uint32_t length) {
return sim_event_import(input, length);
}
static uint64_t hash_payload(const uint8_t *payload, uint32_t payload_len) {
uint64_t hash = 1469598103934665603ULL;
uint32_t index;

View File

@@ -87,6 +87,24 @@ typedef struct {
static hal_state state;
uint32_t sim_hal_checkpoint_size(void) { return (uint32_t)sizeof(hal_state); }
int32_t sim_hal_checkpoint_export(uint8_t *output, uint32_t capacity) {
if (output == NULL || capacity != sizeof(state)) return SIM_HAL_ERR_ARGUMENT;
memcpy(output, &state, sizeof(state));
return SIM_HAL_OK;
}
int32_t sim_hal_checkpoint_restore(const uint8_t *input, uint32_t length) {
if (input == NULL || length != sizeof(state)) return SIM_HAL_ERR_ARGUMENT;
const hal_state *saved = (const hal_state *)input;
for (uint32_t i = 0; i < SIM_HAL_PIN_CAPACITY; ++i) if (saved->pins[i].used && (memchr(saved->pins[i].name, '\0', SIM_HAL_NAME_CAPACITY) == NULL || saved->pins[i].value.type < SIM_HAL_BIT || saved->pins[i].value.type > SIM_HAL_FLOAT)) return SIM_HAL_ERR_ARGUMENT;
for (uint32_t i = 0; i < SIM_HAL_FUNCTION_CAPACITY; ++i) if (saved->functions[i].used && (saved->functions[i].thread >= SIM_HAL_THREAD_CAPACITY || (saved->functions[i].component_index != UINT32_MAX && saved->functions[i].component_index >= SIM_HAL_COMPONENT_CAPACITY))) return SIM_HAL_ERR_ARGUMENT;
for (uint32_t i = 0; i < sizeof(saved->links) / sizeof(saved->links[0]); ++i) if (saved->links[i].used && (saved->links[i].source >= SIM_HAL_PIN_CAPACITY || saved->links[i].target >= SIM_HAL_PIN_CAPACITY)) return SIM_HAL_ERR_ARGUMENT;
memcpy(&state, saved, sizeof(state));
return SIM_HAL_OK;
}
static int valid_name(const char *name) {
return name != NULL && name[0] != '\0' && strlen(name) < SIM_HAL_NAME_CAPACITY;
}

View File

@@ -85,6 +85,28 @@ static int code_is(double value, int integer_code) {
return isfinite(value) && fabs(value - (double)integer_code) <= 1e-12;
}
typedef struct { uint32_t magic, size; sim_interpreter interpreter; interpreter_meta meta; } interpreter_checkpoint;
uint32_t sim_interpreter_checkpoint_size(void) { return sizeof(interpreter_checkpoint); }
int32_t sim_interpreter_checkpoint_export(const sim_interpreter *interpreter, uint8_t *output, uint32_t capacity) {
const interpreter_meta *meta = meta_for(interpreter, 0);
if (meta == NULL || output == NULL || capacity != sizeof(interpreter_checkpoint)) return SIM_INTERP_ERR_ARGUMENT;
interpreter_checkpoint *saved = (interpreter_checkpoint *)output;
memset(saved, 0, sizeof(*saved));
saved->magic = UINT32_C(0x49504331); saved->size = sizeof(*saved);
saved->interpreter = *interpreter; saved->meta = *meta; saved->meta.owner = NULL;
return SIM_INTERP_OK;
}
int32_t sim_interpreter_checkpoint_restore(sim_interpreter *interpreter, const uint8_t *input, uint32_t length) {
if (interpreter == NULL || input == NULL || length != sizeof(interpreter_checkpoint)) return SIM_INTERP_ERR_ARGUMENT;
const interpreter_checkpoint *saved = (const interpreter_checkpoint *)input;
if (saved->magic != UINT32_C(0x49504331) || saved->size != sizeof(*saved) || saved->meta.owner != NULL || !finite_state(&saved->interpreter.state) || saved->interpreter.canonical_length >= SIM_INTERPRETER_CANONICAL_CAPACITY || saved->meta.coordinate_system < 0 || saved->meta.coordinate_system > 9) return SIM_INTERP_ERR_ARGUMENT;
for (unsigned i = 0; i < NAMED_PARAMETER_CAPACITY; ++i) if (saved->meta.named[i].used && memchr(saved->meta.named[i].name, '\0', NAMED_PARAMETER_LENGTH) == NULL) return SIM_INTERP_ERR_ARGUMENT;
interpreter_meta *meta = meta_for(interpreter, 1);
if (meta == NULL) return SIM_INTERP_ERR_ARGUMENT;
*interpreter = saved->interpreter; *meta = saved->meta; meta->owner = interpreter;
return SIM_INTERP_OK;
}
static int code_is_value(double value, double expected) {
return isfinite(value) && fabs(value - expected) <= 1e-12;
}

View File

@@ -22,6 +22,7 @@ struct sim_linuxcnc_tp {
double axis_max[9];
double velocity_scale;
double jog_scale;
double scale_minimum;
uint32_t tick;
uint32_t last_active_id;
int just_completed;
@@ -31,6 +32,45 @@ struct sim_linuxcnc_tp {
static sim_linuxcnc_tp *active_planner;
/* Same-build checkpoint: TP/TC contain the integration and blend state which
cannot be reconstructed by re-enqueuing the remaining geometric path.
The queue pointer is removed on export and rebound on restore. All other
members of these source structs are values, not process addresses. */
typedef struct {
uint32_t magic, planner_size, tc_size, capacity;
sim_linuxcnc_tp planner;
TC_STRUCT queue[TP_DEFAULT_QUEUE_SIZE];
} tp_checkpoint;
uint32_t sim_linuxcnc_tp_checkpoint_size(void) { return (uint32_t)sizeof(tp_checkpoint); }
int32_t sim_linuxcnc_tp_checkpoint_export(const sim_linuxcnc_tp *planner, uint8_t *output, uint32_t capacity) {
if (planner == NULL || !planner->initialized || output == NULL || capacity != sizeof(tp_checkpoint) || planner->tp.queue.size != TP_DEFAULT_QUEUE_SIZE) return -1228;
tp_checkpoint *saved = (tp_checkpoint *)output;
memset(saved, 0, sizeof(*saved));
saved->magic = UINT32_C(0x54504331);
saved->planner_size = sizeof(sim_linuxcnc_tp); saved->tc_size = sizeof(TC_STRUCT); saved->capacity = TP_DEFAULT_QUEUE_SIZE;
saved->planner = *planner;
saved->planner.tp.queue.queue = NULL;
memcpy(saved->queue, planner->tp.queue.queue, sizeof(saved->queue));
return 0;
}
int32_t sim_linuxcnc_tp_checkpoint_restore(sim_linuxcnc_tp *planner, const uint8_t *input, uint32_t length) {
if (planner == NULL || !planner->initialized || input == NULL || length != sizeof(tp_checkpoint)) return -1228;
const tp_checkpoint *saved = (const tp_checkpoint *)input;
const TC_QUEUE_STRUCT *queue = &saved->planner.tp.queue;
if (saved->magic != UINT32_C(0x54504331) || saved->planner_size != sizeof(sim_linuxcnc_tp) || saved->tc_size != sizeof(TC_STRUCT) || saved->capacity != TP_DEFAULT_QUEUE_SIZE || saved->planner.initialized != 1 || queue->queue != NULL || queue->size != TP_DEFAULT_QUEUE_SIZE || queue->start < 0 || queue->start >= queue->size || queue->end < 0 || queue->end >= queue->size || queue->_len < 0 || queue->_rlen < 0 || queue->_len > queue->size || queue->_rlen > queue->size - queue->_len) return -1228;
if (!isfinite(saved->planner.tp.cycleTime) || saved->planner.tp.cycleTime != planner->tp.cycleTime || memcmp(&saved->planner.config, &planner->config, sizeof(planner->config)) != 0 || memcmp(saved->planner.axis_min, planner->axis_min, sizeof(planner->axis_min)) != 0 || memcmp(saved->planner.axis_max, planner->axis_max, sizeof(planner->axis_max)) != 0 || memcmp(saved->planner.axis_velocity, planner->axis_velocity, sizeof(planner->axis_velocity)) != 0 || memcmp(saved->planner.axis_acceleration, planner->axis_acceleration, sizeof(planner->axis_acceleration)) != 0) return -1229;
TC_STRUCT *storage = planner->tp.queue.queue;
*planner = saved->planner;
planner->tp.queue.queue = storage;
memcpy(storage, saved->queue, sizeof(saved->queue));
active_planner = planner;
tpMotData(&planner->status, &planner->config);
return 0;
}
static void noop_dio(int index, char value) { (void)index; (void)value; }
static void noop_aio(int index, double value) { (void)index; (void)value; }
static void noop_rotary_unlock(int index, int value) { (void)index; (void)value; }
@@ -60,7 +100,7 @@ static void xyzac_line_limits(const sim_linuxcnc_tp *planner, const EmcPose *sta
const double distance = has_linear ? sqrt(linear_length) : has_angular ? sqrt(angular_length) : 0.0;
if (!(distance > 0.0)) return;
if (max_velocity_time > 0.0) *velocity = fmin(*velocity, distance / max_velocity_time);
if (max_acceleration_time > 0.0) *acceleration = fmax(*acceleration, distance / max_acceleration_time);
if (max_acceleration_time > 0.0) *acceleration = fmin(*acceleration, distance / max_acceleration_time);
}
static void xyzac_circle_limits(const sim_linuxcnc_tp *planner, const EmcPose *start, const EmcPose *end, const PmCartesian *center, const PmCartesian *normal, int turn, double *velocity, double *initial_velocity, double *acceleration) {
@@ -95,7 +135,7 @@ static void xyzac_circle_limits(const sim_linuxcnc_tp *planner, const EmcPose *s
const double max_acceleration = total_xyz_length / fmax(time_motion_acceleration, time_spiral_acceleration);
*velocity = fmin(*velocity, max_velocity);
*initial_velocity = max_velocity;
*acceleration = fmax(*acceleration, max_acceleration);
*acceleration = fmin(*acceleration, max_acceleration);
}
static EmcPose to_emc_pose(const sim_posemath_pose *value) {
@@ -143,18 +183,17 @@ int32_t sim_linuxcnc_tp_init(sim_linuxcnc_tp *planner, double cycle_time, double
planner->config.arcBlendEnable = 1; planner->config.arcBlendFallbackEnable = 0;
planner->config.arcBlendOptDepth = 50; planner->config.arcBlendGapCycles = 4;
planner->config.arcBlendRampFreq = 100.0; planner->config.arcBlendTangentKinkRatio = 0.1;
// Match xyzac-trt.ini joint limits: the trajectory cap is 35 mm/s, but
// XYZ joints are limited to 20 and rotary A/C joints to 30 deg/s. The
// upstream TP uses these per-axis callbacks when calculating tangent
// velocity and acceleration at a corner.
// The caller configures each axis from INI before motion is queued.
// Standalone planners use their supplied trajectory limits.
for (int axis = 0; axis < 9; ++axis) {
planner->axis_velocity[axis] = axis < 3 ? fmin(20.0, velocity_limit) : (axis == 3 || axis == 5) ? fmin(30.0, velocity_limit) : velocity_limit;
planner->axis_velocity[axis] = velocity_limit;
planner->axis_acceleration[axis] = acceleration_limit;
planner->axis_min[axis] = -INFINITY;
planner->axis_max[axis] = INFINITY;
}
planner->velocity_scale = 1.0;
planner->jog_scale = 1.0;
planner->scale_minimum = 0.0;
planner->status.feed_scale = 1.0; planner->status.rapid_scale = 1.0; planner->status.net_feed_scale = 1.0;
planner->status.enables_new = FS_ENABLED | FH_ENABLED; planner->status.enables_queued = planner->status.enables_new;
planner->status.motionFlag = EMCMOT_MOTION_ENABLE_BIT | EMCMOT_MOTION_COORD_BIT | EMCMOT_MOTION_INPOS_BIT;
@@ -182,12 +221,12 @@ int32_t sim_linuxcnc_tp_set_profile(sim_linuxcnc_tp *planner, int profile, doubl
}
int32_t sim_linuxcnc_tp_set_feed_override(sim_linuxcnc_tp *planner, double scale) {
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.0 || scale > 2.0) return -1207;
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.0 || scale > planner->config.maxFeedScale) return -1207;
planner->status.net_feed_scale = scale; planner->status.feed_scale = scale; return 0;
}
int32_t sim_linuxcnc_tp_set_velocity_scale(sim_linuxcnc_tp *planner, double scale) {
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.06 || scale > 1.0) return -1211;
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale <= 0.0 || scale < planner->scale_minimum || scale > 1.0) return -1211;
active_planner = planner;
if (tpSetVlimit(&planner->tp, planner->config.limitVel * scale) != TP_ERR_OK) return -1212;
planner->velocity_scale = scale;
@@ -195,7 +234,7 @@ int32_t sim_linuxcnc_tp_set_velocity_scale(sim_linuxcnc_tp *planner, double scal
}
int32_t sim_linuxcnc_tp_set_jog_scale(sim_linuxcnc_tp *planner, double scale) {
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale < 0.06 || scale > 1.0) return -1213;
if (planner == NULL || !planner->initialized || !isfinite(scale) || scale <= 0.0 || scale < planner->scale_minimum || scale > 1.0) return -1213;
planner->jog_scale = scale;
return 0;
}
@@ -207,6 +246,36 @@ int32_t sim_linuxcnc_tp_set_axis_limit(sim_linuxcnc_tp *planner, int axis, doubl
return 0;
}
int32_t sim_linuxcnc_tp_set_axis_dynamics(sim_linuxcnc_tp *planner, int axis, double velocity, double acceleration) {
if (planner == NULL || !planner->initialized || axis < 0 || axis >= 9 || !isfinite(velocity) || velocity <= 0.0 || !isfinite(acceleration) || acceleration <= 0.0) return -1214;
planner->axis_velocity[axis] = velocity;
planner->axis_acceleration[axis] = acceleration;
return 0;
}
int32_t sim_linuxcnc_tp_set_feed_limit(sim_linuxcnc_tp *planner, double maximum) {
if (planner == NULL || !planner->initialized || !isfinite(maximum) || maximum < 1.0) return -1207;
planner->config.maxFeedScale = maximum;
return 0;
}
int32_t sim_linuxcnc_tp_set_scale_minimum(sim_linuxcnc_tp *planner, double minimum) {
if (planner == NULL || !planner->initialized || !isfinite(minimum) || minimum <= 0.0 || minimum > 1.0) return -1211;
planner->scale_minimum = minimum;
return 0;
}
int32_t sim_linuxcnc_tp_set_blend(sim_linuxcnc_tp *planner, int enable, int fallback, int depth, int gap, double ramp, double kink) {
if (planner == NULL || !planner->initialized || (enable != 0 && enable != 1) || (fallback != 0 && fallback != 1) || depth < 1 || gap < 0 || !isfinite(ramp) || ramp <= 0.0 || !isfinite(kink) || kink <= 0.0) return -1205;
planner->config.arcBlendEnable = enable;
planner->config.arcBlendFallbackEnable = fallback;
planner->config.arcBlendOptDepth = depth;
planner->config.arcBlendGapCycles = gap;
planner->config.arcBlendRampFreq = ramp;
planner->config.arcBlendTangentKinkRatio = kink;
return 0;
}
int32_t sim_linuxcnc_tp_jog(sim_linuxcnc_tp *planner, int axis, int direction, double increment, uint32_t tag) {
EmcPose end;
double *value;

View File

@@ -318,6 +318,30 @@ static void reset_runtime(sim_program *program) {
uint32_t sim_program_size(void) { return (uint32_t)sizeof(sim_program); }
typedef struct { uint32_t magic, size; sim_program program; } program_checkpoint;
uint32_t sim_program_checkpoint_size(void) { return sizeof(program_checkpoint) + sim_interpreter_checkpoint_size(); }
int32_t sim_program_checkpoint_export(const sim_program *program, uint8_t *output, uint32_t capacity) {
if (program == NULL || output == NULL || capacity != sim_program_checkpoint_size()) return SIM_PROGRAM_ERR_ARGUMENT;
program_checkpoint *saved = (program_checkpoint *)output;
memset(saved, 0, sizeof(*saved)); saved->magic = UINT32_C(0x50524331); saved->size = sizeof(*saved);
saved->program = *program;
saved->program.source = NULL; saved->program.line_offsets = NULL;
saved->program.source_capacity = 0; saved->program.line_capacity = 0;
return sim_interpreter_checkpoint_export(&program->interpreter, output + sizeof(*saved), sim_interpreter_checkpoint_size()) == SIM_INTERP_OK ? SIM_PROGRAM_OK : SIM_PROGRAM_ERR_INTERPRETER;
}
int32_t sim_program_checkpoint_restore(sim_program *program, const uint8_t *input, uint32_t length) {
if (program == NULL || input == NULL || length != sim_program_checkpoint_size()) return SIM_PROGRAM_ERR_ARGUMENT;
const program_checkpoint *saved = (const program_checkpoint *)input;
if (saved->magic != UINT32_C(0x50524331) || saved->size != sizeof(*saved) || saved->program.source != NULL || saved->program.line_offsets != NULL || saved->program.source_length != program->source_length || saved->program.line_count != program->line_count || saved->program.current_line > program->line_count || saved->program.call_depth > SIM_PROGRAM_CALL_DEPTH || saved->program.subroutine_count > SIM_PROGRAM_SUBROUTINE_CAPACITY || saved->program.status > SIM_PROGRAM_ERROR) return SIM_PROGRAM_ERR_ARGUMENT;
if (sim_interpreter_checkpoint_restore(&program->interpreter, input + sizeof(*saved), sim_interpreter_checkpoint_size()) != SIM_INTERP_OK) return SIM_PROGRAM_ERR_INTERPRETER;
char *source = program->source; uint32_t *lines = program->line_offsets;
uint32_t source_capacity = program->source_capacity, line_capacity = program->line_capacity;
*program = saved->program;
program->source = source; program->line_offsets = lines;
program->source_capacity = source_capacity; program->line_capacity = line_capacity;
return SIM_PROGRAM_OK;
}
int32_t sim_program_init(sim_program *program) {
if (program == NULL) return SIM_PROGRAM_ERR_ARGUMENT;
memset(program, 0, sizeof(*program));

View File

@@ -12,6 +12,18 @@ typedef struct {
static protocol_state state;
uint32_t sim_protocol_checkpoint_size(void) { return sizeof(state); }
int32_t sim_protocol_checkpoint_export(uint8_t *output, uint32_t capacity) {
if (output == NULL || capacity != sizeof(state)) return SIM_PROTOCOL_ERR_ARGUMENT;
memcpy(output, &state, sizeof(state)); return SIM_PROTOCOL_OK;
}
int32_t sim_protocol_checkpoint_restore(const uint8_t *input, uint32_t length) {
if (input == NULL || length != sizeof(state)) return SIM_PROTOCOL_ERR_ARGUMENT;
const protocol_state *saved = (const protocol_state *)input;
if (saved->state > SIM_PROTOCOL_DESTROYED) return SIM_PROTOCOL_ERR_ARGUMENT;
memcpy(&state, saved, sizeof(state)); return SIM_PROTOCOL_OK;
}
static int has_text(const char *value) {
return value != NULL && value[0] != '\0' && strlen(value) <= 128;
}

View File

@@ -2,6 +2,8 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static float get_float(const char *name) {
sim_hal_value value = {0};
@@ -32,6 +34,19 @@ int sim_hal_test_run(void) {
assert(sim_hal_register_function("missing", "bad") == SIM_HAL_ERR_NOT_FOUND);
assert(sim_hal_step(5) == SIM_HAL_OK);
assert(sim_hal_tick() == 5 && sim_hal_function_calls("update") == 2);
const uint32_t saved_size = sim_hal_checkpoint_size();
uint8_t *saved = malloc(saved_size), *restored = malloc(saved_size);
assert(saved && restored);
assert(sim_hal_checkpoint_export(saved, saved_size) == SIM_HAL_OK);
assert(sim_hal_step(3) == SIM_HAL_OK);
assert(sim_hal_tick() == 8 && sim_hal_function_calls("update") == 4);
assert(sim_hal_checkpoint_restore(saved, saved_size - 1) == SIM_HAL_ERR_ARGUMENT);
assert(sim_hal_tick() == 8);
assert(sim_hal_checkpoint_restore(saved, saved_size) == SIM_HAL_OK);
assert(sim_hal_tick() == 5 && sim_hal_function_calls("update") == 2);
assert(sim_hal_checkpoint_export(restored, saved_size) == SIM_HAL_OK);
assert(memcmp(saved, restored, saved_size) == 0);
free(saved); free(restored);
assert(sim_hal_set_function_failure("update", 1) == SIM_HAL_OK);
assert(sim_hal_step(1) == SIM_HAL_ERR_FUNCTION);
assert(sim_hal_tick() == 5);

View File

@@ -18,6 +18,26 @@ int main(void) {
sim_posemath_pose first = pose(0, 0, 10);
sim_posemath_pose second = pose(20, 20, 10);
assert(planner != NULL);
assert(sim_linuxcnc_tp_init(planner, .001, 35, 300) == 0);
assert(sim_linuxcnc_tp_set_axis_dynamics(planner, 0, 7, 40) == 0);
assert(fabs(sim_linuxcnc_tp_jog_velocity(planner, 0) - 7) < 1e-9);
assert(sim_linuxcnc_tp_set_axis_dynamics(planner, 4, 11, 20) == 0);
assert(fabs(sim_linuxcnc_tp_jog_velocity(planner, 4) - 11) < 1e-9);
assert(sim_linuxcnc_tp_set_axis_dynamics(planner, 9, 7, 40) != 0);
assert(sim_linuxcnc_tp_set_axis_dynamics(planner, 0, NAN, 40) != 0);
assert(sim_linuxcnc_tp_set_axis_dynamics(planner, 0, 7, 0) != 0);
assert(sim_linuxcnc_tp_set_feed_limit(planner, 1.3) == 0);
assert(sim_linuxcnc_tp_set_feed_override(planner, 1.3) == 0);
assert(sim_linuxcnc_tp_set_feed_override(planner, 1.31) != 0);
assert(sim_linuxcnc_tp_set_scale_minimum(planner, .02) == 0);
assert(sim_linuxcnc_tp_set_jog_scale(planner, .02) == 0);
assert(sim_linuxcnc_tp_set_velocity_scale(planner, .02) == 0);
assert(sim_linuxcnc_tp_set_jog_scale(planner, .01) != 0);
assert(sim_linuxcnc_tp_set_scale_minimum(planner, NAN) != 0);
assert(sim_linuxcnc_tp_set_blend(planner, 1, 0, 25, 3, 50, .2) == 0);
assert(sim_linuxcnc_tp_set_blend(planner, 2, 0, 25, 3, 50, .2) != 0);
assert(sim_linuxcnc_tp_set_blend(planner, 1, 0, 0, 3, 50, .2) != 0);
assert(sim_linuxcnc_tp_set_blend(planner, 1, 0, 25, 3, NAN, .2) != 0);
assert(sim_linuxcnc_tp_init(planner, 0.001, 35.0, 300.0) == 0);
assert(sim_linuxcnc_tp_queue_capacity(planner) > 0);
assert(sim_linuxcnc_tp_velocity_mm_min(planner) == 0.0);
@@ -25,6 +45,28 @@ int main(void) {
assert(sim_linuxcnc_tp_set_profile(planner, 0, 0.0) == 0);
assert(sim_linuxcnc_tp_add_line(planner, &first, 20.0, 300.0, 16, 1) == 0);
assert(sim_linuxcnc_tp_add_line(planner, &second, 35.0, 300.0, 18, 1) == 0);
for (int index = 0; index < 20; ++index) assert(sim_linuxcnc_tp_step(planner) == 0);
uint32_t checkpoint_size = sim_linuxcnc_tp_checkpoint_size();
uint8_t *checkpoint = malloc(checkpoint_size);
uint64_t continuation[100];
assert(checkpoint != NULL);
assert(sim_linuxcnc_tp_checkpoint_export(planner, checkpoint, checkpoint_size) == 0);
for (int index = 0; index < 100; ++index) {
assert(sim_linuxcnc_tp_step(planner) == 0);
continuation[index] = sim_linuxcnc_tp_state_hash(planner);
}
const uint64_t before_invalid = sim_linuxcnc_tp_state_hash(planner);
assert(sim_linuxcnc_tp_checkpoint_restore(planner, checkpoint, checkpoint_size - 1) != 0);
checkpoint[0] ^= 1;
assert(sim_linuxcnc_tp_checkpoint_restore(planner, checkpoint, checkpoint_size) != 0);
assert(sim_linuxcnc_tp_state_hash(planner) == before_invalid);
checkpoint[0] ^= 1;
assert(sim_linuxcnc_tp_checkpoint_restore(planner, checkpoint, checkpoint_size) == 0);
for (int index = 0; index < 100; ++index) {
assert(sim_linuxcnc_tp_step(planner) == 0);
assert(sim_linuxcnc_tp_state_hash(planner) == continuation[index]);
}
free(checkpoint);
for (int index = 0; index < 5000; ++index) {
int result = sim_linuxcnc_tp_step(planner);
if (result != 0) fprintf(stderr, "tp_step[%d]=%d\\n", index, result);