Initial LinuxCNC WebAssembly workspace snapshot

This commit is contained in:
2026-08-20 18:21:15 +08:00
commit 5dd614c335
92 changed files with 200203 additions and 0 deletions

70
wasm-core/CMakeLists.txt Normal file
View File

@@ -0,0 +1,70 @@
cmake_minimum_required(VERSION 3.20)
project(linuxcnc_web_wasm_core LANGUAGES C CXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(sim_core STATIC src/sim_api.c src/sim_protocol.c src/sim_event.c src/sim_hal.c src/sim_posemath.c)
target_include_directories(sim_core PUBLIC include)
target_compile_definitions(sim_core PRIVATE SIM_API_BUILD=1)
add_library(sim_posemath STATIC
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/libnml/posemath/_posemath.c
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/libnml/posemath/gomath.c
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/libnml/posemath/sincos.c
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/libnml/posemath/posemath.cc
)
target_include_directories(sim_posemath PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/rtapi
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/libnml/posemath
)
target_compile_definitions(sim_posemath PRIVATE ULAPI)
target_link_libraries(sim_core PUBLIC sim_posemath)
if(NOT EMSCRIPTEN)
add_executable(sim_api_test tests/sim_api_test.c)
target_link_libraries(sim_api_test PRIVATE sim_core)
target_include_directories(sim_api_test PRIVATE include)
enable_testing()
add_test(NAME sim_api_test COMMAND sim_api_test)
add_executable(sim_protocol_test tests/sim_protocol_test.c)
target_link_libraries(sim_protocol_test PRIVATE sim_core)
target_include_directories(sim_protocol_test PRIVATE include)
add_test(NAME sim_protocol_test COMMAND sim_protocol_test)
add_executable(sim_event_test tests/sim_event_test.c)
target_link_libraries(sim_event_test PRIVATE sim_core)
target_include_directories(sim_event_test PRIVATE include)
add_test(NAME sim_event_test COMMAND sim_event_test)
add_executable(sim_hal_test tests/sim_hal_test.c)
target_link_libraries(sim_hal_test PRIVATE sim_core)
target_include_directories(sim_hal_test PRIVATE include)
add_test(NAME sim_hal_test COMMAND sim_hal_test)
add_executable(sim_posemath_test tests/sim_posemath_test.c)
target_link_libraries(sim_posemath_test PRIVATE sim_core)
target_include_directories(sim_posemath_test PRIVATE include)
add_test(NAME sim_posemath_test COMMAND sim_posemath_test)
endif()
if(EMSCRIPTEN)
add_executable(sim_core_module src/sim_api.c src/sim_protocol.c src/sim_event.c src/sim_hal.c src/sim_posemath.c)
target_include_directories(sim_core_module PRIVATE
include
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/rtapi
${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc/src/libnml/posemath
)
target_link_libraries(sim_core_module PRIVATE sim_posemath)
set_target_properties(sim_core_module PROPERTIES
OUTPUT_NAME "linuxcnc-sim-core"
SUFFIX ".js"
LINK_FLAGS "-sMODULARIZE=1 -sEXPORT_ES6=1 -sENVIRONMENT=web,worker -sEXPORTED_FUNCTIONS=['_malloc','_free','_sim_create','_sim_load_workspace','_sim_command','_sim_step','_sim_read_snapshot','_sim_read_event','_sim_export_state','_sim_import_state','_sim_destroy','_sim_protocol_reset','_sim_protocol_accept','_sim_protocol_pause','_sim_protocol_resume','_sim_protocol_state_value','_sim_protocol_last_seq','_sim_protocol_tick','_sim_protocol_set_tick','_sim_event_reset','_sim_event_append','_sim_event_count','_sim_event_cursor','_sim_event_read','_sim_event_export','_sim_event_import','_sim_hal_reset','_sim_hal_register_pin','_sim_hal_set_pin','_sim_hal_get_pin','_sim_hal_connect','_sim_hal_register_thread','_sim_hal_register_function','_sim_hal_set_function_failure','_sim_hal_step','_sim_hal_tick','_sim_hal_function_calls','_sim_hal_pin_count','_sim_hal_thread_count','_sim_hal_function_count','_sim_posemath_normalize','_sim_posemath_dot','_sim_posemath_line_point','_sim_posemath_is_finite'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap']"
)
endif()

View File

@@ -0,0 +1,34 @@
#ifndef LINUXCNC_WEB_WASM_SIM_API_H
#define LINUXCNC_WEB_WASM_SIM_API_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
enum sim_result {
SIM_OK = 0,
SIM_ERR_INVALID_ARGUMENT = -1,
SIM_ERR_NOT_CREATED = -2,
SIM_ERR_BUFFER_TOO_SMALL = -3,
SIM_ERR_INVALID_STATE = -4,
SIM_ERR_UNSUPPORTED = -5,
};
/* Bootstrap ABI only. LinuxCNC behavior is added behind this stable boundary. */
int32_t sim_create(const uint8_t *config, uint32_t config_len);
int32_t sim_load_workspace(const char *root_path);
int32_t sim_command(const uint8_t *command, uint32_t command_len);
int32_t sim_step(uint32_t cycles);
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);
void sim_destroy(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,42 @@
#ifndef LINUXCNC_WEB_WASM_SIM_EVENT_H
#define LINUXCNC_WEB_WASM_SIM_EVENT_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SIM_EVENT_KIND_CAPACITY 32U
#define SIM_EVENT_CAPACITY 256U
enum sim_event_result {
SIM_EVENT_OK = 0,
SIM_EVENT_ERR_SEQUENCE = -200,
SIM_EVENT_ERR_TICK = -201,
SIM_EVENT_ERR_ARGUMENT = -202,
SIM_EVENT_ERR_FULL = -203,
SIM_EVENT_ERR_BUFFER = -204,
};
typedef struct sim_event_record {
uint32_t seq;
uint32_t tick;
uint64_t payload_hash;
char kind[SIM_EVENT_KIND_CAPACITY];
} sim_event_record;
void sim_event_reset(void);
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);
uint32_t sim_event_cursor(void);
uint32_t sim_event_read(uint32_t index, uint8_t *out, uint32_t capacity);
uint32_t sim_event_export(uint8_t *out, uint32_t capacity);
int32_t sim_event_import(const uint8_t *data, uint32_t len);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,64 @@
#ifndef LINUXCNC_WEB_WASM_SIM_HAL_H
#define LINUXCNC_WEB_WASM_SIM_HAL_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SIM_HAL_NAME_CAPACITY 48U
#define SIM_HAL_PIN_CAPACITY 64U
#define SIM_HAL_THREAD_CAPACITY 16U
#define SIM_HAL_FUNCTION_CAPACITY 64U
enum sim_hal_result {
SIM_HAL_OK = 0,
SIM_HAL_ERR_ARGUMENT = -300,
SIM_HAL_ERR_FULL = -301,
SIM_HAL_ERR_DUPLICATE = -302,
SIM_HAL_ERR_NOT_FOUND = -303,
SIM_HAL_ERR_TYPE = -304,
SIM_HAL_ERR_RANGE = -305,
SIM_HAL_ERR_CONNECTION = -306,
SIM_HAL_ERR_FUNCTION = -307,
SIM_HAL_ERR_REAL_EXCLUDED = -308,
};
enum sim_hal_type {
SIM_HAL_BIT = 1,
SIM_HAL_S32 = 2,
SIM_HAL_U32 = 3,
SIM_HAL_FLOAT = 4,
};
typedef struct sim_hal_value {
uint32_t type;
union {
uint32_t bit;
int32_t s32;
uint32_t u32;
float real;
} value;
} sim_hal_value;
void sim_hal_reset(void);
int32_t sim_hal_register_pin(const char *name, uint32_t type);
int32_t sim_hal_set_pin(const char *name, sim_hal_value value);
int32_t sim_hal_get_pin(const char *name, sim_hal_value *out);
int32_t sim_hal_connect(const char *source, const char *target);
int32_t sim_hal_register_thread(const char *name, uint32_t period_ticks);
int32_t sim_hal_register_function(const char *thread, const char *name);
int32_t sim_hal_set_function_failure(const char *name, uint32_t fail);
int32_t sim_hal_step(uint32_t ticks);
uint32_t sim_hal_tick(void);
uint32_t sim_hal_function_calls(const char *name);
uint32_t sim_hal_pin_count(void);
uint32_t sim_hal_thread_count(void);
uint32_t sim_hal_function_count(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,32 @@
#ifndef LINUXCNC_WEB_WASM_SIM_POSEMATH_H
#define LINUXCNC_WEB_WASM_SIM_POSEMATH_H
#ifdef __cplusplus
extern "C" {
#endif
enum sim_posemath_result {
SIM_POSEMATH_OK = 0,
SIM_POSEMATH_ERR_ARGUMENT = -400,
SIM_POSEMATH_ERR_NONFINITE = -401,
SIM_POSEMATH_ERR_DEGENERATE = -402,
SIM_POSEMATH_ERR_RANGE = -403,
};
typedef struct sim_posemath_cartesian {
double x;
double y;
double z;
} sim_posemath_cartesian;
int sim_posemath_normalize(const sim_posemath_cartesian *input, sim_posemath_cartesian *output);
int sim_posemath_dot(const sim_posemath_cartesian *left, const sim_posemath_cartesian *right, double *output);
int sim_posemath_line_point(const sim_posemath_cartesian *start, const sim_posemath_cartesian *end,
double fraction, sim_posemath_cartesian *output);
int sim_posemath_is_finite(const sim_posemath_cartesian *value);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,46 @@
#ifndef LINUXCNC_WEB_WASM_SIM_PROTOCOL_H
#define LINUXCNC_WEB_WASM_SIM_PROTOCOL_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
enum sim_protocol_result {
SIM_PROTOCOL_OK = 0,
SIM_PROTOCOL_ERR_SCHEMA = -100,
SIM_PROTOCOL_ERR_SEQUENCE = -101,
SIM_PROTOCOL_ERR_STATE = -102,
SIM_PROTOCOL_ERR_OPERATION_ID = -103,
SIM_PROTOCOL_ERR_KIND = -104,
SIM_PROTOCOL_ERR_ARGUMENT = -105,
SIM_PROTOCOL_ERR_TICK = -106,
};
enum sim_protocol_state {
SIM_PROTOCOL_NEW = 0,
SIM_PROTOCOL_HELLO = 1,
SIM_PROTOCOL_ATTACHED = 2,
SIM_PROTOCOL_READY = 3,
SIM_PROTOCOL_PAUSED = 4,
SIM_PROTOCOL_DESTROYED = 5,
};
/* The protocol validator is deliberately independent of JSON and malloc. */
void sim_protocol_reset(void);
int32_t sim_protocol_accept(uint32_t schema_version, uint32_t seq,
const char *kind, const char *operation_id,
uint32_t simulation_tick);
int32_t sim_protocol_pause(void);
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);
int32_t sim_protocol_set_tick(uint32_t simulation_tick);
#ifdef __cplusplus
}
#endif
#endif

79
wasm-core/src/sim_api.c Normal file
View File

@@ -0,0 +1,79 @@
#include "sim_api.h"
#include <string.h>
typedef struct {
uint32_t created;
uint32_t loaded;
uint64_t tick;
uint32_t pending_event;
} sim_state;
static sim_state state;
static int valid_buffer(const uint8_t *data, uint32_t len) {
return len == 0 || data != NULL;
}
int32_t sim_create(const uint8_t *config, uint32_t config_len) {
if (!valid_buffer(config, config_len)) return SIM_ERR_INVALID_ARGUMENT;
memset(&state, 0, sizeof(state));
state.created = 1;
state.pending_event = 1;
return SIM_OK;
}
int32_t sim_load_workspace(const char *root_path) {
if (!state.created) return SIM_ERR_NOT_CREATED;
if (root_path == NULL || root_path[0] == '\0') return SIM_ERR_INVALID_ARGUMENT;
state.loaded = 1;
state.pending_event = 1;
return SIM_OK;
}
int32_t sim_command(const uint8_t *command, uint32_t command_len) {
if (!state.created || !state.loaded) return SIM_ERR_INVALID_STATE;
if (!valid_buffer(command, command_len) || command_len == 0) return SIM_ERR_INVALID_ARGUMENT;
state.pending_event = 1;
return SIM_OK;
}
int32_t sim_step(uint32_t cycles) {
if (!state.created || !state.loaded) return SIM_ERR_INVALID_STATE;
state.tick += cycles;
state.pending_event = 1;
return SIM_OK;
}
uint32_t sim_read_snapshot(uint8_t *out, uint32_t capacity) {
const uint32_t size = (uint32_t)sizeof(state.tick);
if (out == NULL || capacity < size) return size;
memcpy(out, &state.tick, size);
return size;
}
uint32_t sim_read_event(uint8_t *out, uint32_t capacity) {
const uint32_t size = 1;
if (!state.pending_event) return 0;
if (out == NULL || capacity < size) return size;
out[0] = 1;
state.pending_event = 0;
return size;
}
int32_t sim_export_state(uint8_t *out, uint32_t capacity) {
const uint32_t size = (uint32_t)sizeof(state);
if (out == NULL || capacity < size) return SIM_ERR_BUFFER_TOO_SMALL;
memcpy(out, &state, size);
return (int32_t)size;
}
int32_t sim_import_state(const uint8_t *data, uint32_t len) {
if (data == NULL || len != sizeof(state)) return SIM_ERR_INVALID_ARGUMENT;
memcpy(&state, data, sizeof(state));
return SIM_OK;
}
void sim_destroy(void) {
memset(&state, 0, sizeof(state));
}

82
wasm-core/src/sim_event.c Normal file
View File

@@ -0,0 +1,82 @@
#include "sim_event.h"
#include <stddef.h>
#include <string.h>
typedef struct sim_event_state {
uint32_t count;
uint32_t last_seq;
uint32_t last_tick;
sim_event_record records[SIM_EVENT_CAPACITY];
} sim_event_state;
static sim_event_state state;
static uint64_t hash_payload(const uint8_t *payload, uint32_t payload_len) {
uint64_t hash = 1469598103934665603ULL;
uint32_t index;
if (payload == NULL && payload_len != 0U) return 0;
for (index = 0; index < payload_len; ++index) {
hash ^= payload[index];
hash *= 1099511628211ULL;
}
return hash;
}
void sim_event_reset(void) {
memset(&state, 0, sizeof(state));
}
int32_t sim_event_append(uint32_t seq, uint32_t tick, const char *kind,
const uint8_t *payload, uint32_t payload_len) {
sim_event_record *record;
size_t kind_len;
if (seq == 0U || seq != state.last_seq + 1U) return SIM_EVENT_ERR_SEQUENCE;
if (tick < state.last_tick) return SIM_EVENT_ERR_TICK;
if (kind == NULL || kind[0] == '\0') return SIM_EVENT_ERR_ARGUMENT;
kind_len = strlen(kind);
if (kind_len >= SIM_EVENT_KIND_CAPACITY || (payload == NULL && payload_len != 0U)) return SIM_EVENT_ERR_ARGUMENT;
if (state.count >= SIM_EVENT_CAPACITY) return SIM_EVENT_ERR_FULL;
record = &state.records[state.count++];
memset(record, 0, sizeof(*record));
memcpy(record->kind, kind, kind_len);
record->seq = seq;
record->tick = tick;
record->payload_hash = hash_payload(payload, payload_len);
state.last_seq = seq;
state.last_tick = tick;
return SIM_EVENT_OK;
}
uint32_t sim_event_count(void) {
return state.count;
}
uint32_t sim_event_cursor(void) {
return state.last_seq;
}
uint32_t sim_event_read(uint32_t index, uint8_t *out, uint32_t capacity) {
const uint32_t size = (uint32_t)sizeof(sim_event_record);
if (index >= state.count) return 0;
if (out == NULL || capacity < size) return size;
memcpy(out, &state.records[index], size);
return size;
}
uint32_t sim_event_export(uint8_t *out, uint32_t capacity) {
const uint32_t size = (uint32_t)sizeof(state);
if (out == NULL || capacity < size) return size;
memcpy(out, &state, size);
return size;
}
int32_t sim_event_import(const uint8_t *data, uint32_t len) {
if (data == NULL || len != sizeof(state)) return SIM_EVENT_ERR_ARGUMENT;
memcpy(&state, data, sizeof(state));
if (state.count > SIM_EVENT_CAPACITY) {
sim_event_reset();
return SIM_EVENT_ERR_ARGUMENT;
}
return SIM_EVENT_OK;
}

166
wasm-core/src/sim_hal.c Normal file
View File

@@ -0,0 +1,166 @@
#include "sim_hal.h"
#include <stddef.h>
#include <string.h>
typedef struct {
uint32_t used;
char name[SIM_HAL_NAME_CAPACITY];
sim_hal_value value;
} pin_entry;
typedef struct {
uint32_t used;
char name[SIM_HAL_NAME_CAPACITY];
uint32_t period;
} thread_entry;
typedef struct {
uint32_t used;
char name[SIM_HAL_NAME_CAPACITY];
uint32_t thread;
uint32_t calls;
uint32_t fail;
} function_entry;
typedef struct {
uint32_t tick;
pin_entry pins[SIM_HAL_PIN_CAPACITY];
thread_entry threads[SIM_HAL_THREAD_CAPACITY];
function_entry functions[SIM_HAL_FUNCTION_CAPACITY];
} hal_state;
static hal_state state;
static int valid_name(const char *name) {
return name != NULL && name[0] != '\0' && strlen(name) < SIM_HAL_NAME_CAPACITY;
}
static int find_pin(const char *name) {
uint32_t index;
for (index = 0; index < SIM_HAL_PIN_CAPACITY; ++index) {
if (state.pins[index].used && strcmp(state.pins[index].name, name) == 0) return (int)index;
}
return -1;
}
static int find_thread(const char *name) {
uint32_t index;
for (index = 0; index < SIM_HAL_THREAD_CAPACITY; ++index) {
if (state.threads[index].used && strcmp(state.threads[index].name, name) == 0) return (int)index;
}
return -1;
}
static int find_function(const char *name) {
uint32_t index;
for (index = 0; index < SIM_HAL_FUNCTION_CAPACITY; ++index) {
if (state.functions[index].used && strcmp(state.functions[index].name, name) == 0) return (int)index;
}
return -1;
}
void sim_hal_reset(void) {
memset(&state, 0, sizeof(state));
}
int32_t sim_hal_register_pin(const char *name, uint32_t type) {
uint32_t index;
if (!valid_name(name) || type < SIM_HAL_BIT || type > SIM_HAL_FLOAT) return SIM_HAL_ERR_ARGUMENT;
if (find_pin(name) >= 0) return SIM_HAL_ERR_DUPLICATE;
for (index = 0; index < SIM_HAL_PIN_CAPACITY; ++index) {
if (!state.pins[index].used) {
state.pins[index].used = 1;
strcpy(state.pins[index].name, name);
state.pins[index].value.type = type;
return SIM_HAL_OK;
}
}
return SIM_HAL_ERR_FULL;
}
int32_t sim_hal_set_pin(const char *name, sim_hal_value value) {
int index = find_pin(name);
if (index < 0) return SIM_HAL_ERR_NOT_FOUND;
if (value.type != state.pins[index].value.type) return SIM_HAL_ERR_TYPE;
if (value.type == SIM_HAL_BIT && value.value.bit > 1U) return SIM_HAL_ERR_RANGE;
state.pins[index].value = value;
return SIM_HAL_OK;
}
int32_t sim_hal_get_pin(const char *name, sim_hal_value *out) {
int index = find_pin(name);
if (index < 0 || out == NULL) return index < 0 ? SIM_HAL_ERR_NOT_FOUND : SIM_HAL_ERR_ARGUMENT;
*out = state.pins[index].value;
return SIM_HAL_OK;
}
int32_t sim_hal_connect(const char *source, const char *target) {
int source_index = find_pin(source);
int target_index = find_pin(target);
if (source_index < 0 || target_index < 0) return SIM_HAL_ERR_NOT_FOUND;
if (state.pins[source_index].value.type != state.pins[target_index].value.type) return SIM_HAL_ERR_CONNECTION;
state.pins[target_index].value = state.pins[source_index].value;
return SIM_HAL_OK;
}
int32_t sim_hal_register_thread(const char *name, uint32_t period_ticks) {
uint32_t index;
if (!valid_name(name) || period_ticks == 0U || period_ticks > 1000000U) return SIM_HAL_ERR_RANGE;
if (find_thread(name) >= 0) return SIM_HAL_ERR_DUPLICATE;
for (index = 0; index < SIM_HAL_THREAD_CAPACITY; ++index) {
if (!state.threads[index].used) {
state.threads[index].used = 1;
strcpy(state.threads[index].name, name);
state.threads[index].period = period_ticks;
return SIM_HAL_OK;
}
}
return SIM_HAL_ERR_FULL;
}
int32_t sim_hal_register_function(const char *thread, const char *name) {
uint32_t index;
int thread_index = find_thread(thread);
if (thread_index < 0) return SIM_HAL_ERR_NOT_FOUND;
if (!valid_name(name)) return SIM_HAL_ERR_ARGUMENT;
if (find_function(name) >= 0) return SIM_HAL_ERR_DUPLICATE;
for (index = 0; index < SIM_HAL_FUNCTION_CAPACITY; ++index) {
if (!state.functions[index].used) {
state.functions[index].used = 1;
strcpy(state.functions[index].name, name);
state.functions[index].thread = (uint32_t)thread_index;
return SIM_HAL_OK;
}
}
return SIM_HAL_ERR_FULL;
}
int32_t sim_hal_set_function_failure(const char *name, uint32_t fail) {
int index = find_function(name);
if (index < 0) return SIM_HAL_ERR_NOT_FOUND;
state.functions[index].fail = fail ? 1U : 0U;
return SIM_HAL_OK;
}
int32_t sim_hal_step(uint32_t ticks) {
uint32_t target = state.tick + ticks;
uint32_t tick;
uint32_t function_index;
for (tick = state.tick + 1U; tick <= target; ++tick) {
for (function_index = 0; function_index < SIM_HAL_FUNCTION_CAPACITY; ++function_index) {
function_entry *function = &state.functions[function_index];
if (!function->used || tick % state.threads[function->thread].period != 0U) continue;
if (function->fail) return SIM_HAL_ERR_FUNCTION;
function->calls += 1U;
}
}
state.tick = target;
return SIM_HAL_OK;
}
uint32_t sim_hal_tick(void) { return state.tick; }
uint32_t sim_hal_function_calls(const char *name) { int index = find_function(name); return index < 0 ? 0U : state.functions[index].calls; }
uint32_t sim_hal_pin_count(void) { uint32_t i, count = 0; for (i = 0; i < SIM_HAL_PIN_CAPACITY; ++i) count += state.pins[i].used; return count; }
uint32_t sim_hal_thread_count(void) { uint32_t i, count = 0; for (i = 0; i < SIM_HAL_THREAD_CAPACITY; ++i) count += state.threads[i].used; return count; }
uint32_t sim_hal_function_count(void) { uint32_t i, count = 0; for (i = 0; i < SIM_HAL_FUNCTION_CAPACITY; ++i) count += state.functions[i].used; return count; }

View File

@@ -0,0 +1,62 @@
#include "sim_posemath.h"
#include "posemath.h"
#include <math.h>
static int finite_cart(const sim_posemath_cartesian *value) {
return value != 0 && isfinite(value->x) && isfinite(value->y) && isfinite(value->z);
}
static PmCartesian to_pm(const sim_posemath_cartesian *value) {
PmCartesian result = {value->x, value->y, value->z};
return result;
}
static void from_pm(const PmCartesian *value, sim_posemath_cartesian *output) {
output->x = value->x;
output->y = value->y;
output->z = value->z;
}
int sim_posemath_is_finite(const sim_posemath_cartesian *value) {
return finite_cart(value);
}
int sim_posemath_normalize(const sim_posemath_cartesian *input, sim_posemath_cartesian *output) {
PmCartesian source;
PmCartesian result;
double magnitude_squared = 0.0;
if (!finite_cart(input) || output == 0) return SIM_POSEMATH_ERR_ARGUMENT;
source = to_pm(input);
if (pmCartMagSq(&source, &magnitude_squared) != 0 || !isfinite(magnitude_squared)) return SIM_POSEMATH_ERR_NONFINITE;
if (magnitude_squared <= 1e-24) return SIM_POSEMATH_ERR_DEGENERATE;
if (pmCartUnit(&source, &result) != 0) return SIM_POSEMATH_ERR_DEGENERATE;
from_pm(&result, output);
return SIM_POSEMATH_OK;
}
int sim_posemath_dot(const sim_posemath_cartesian *left, const sim_posemath_cartesian *right, double *output) {
PmCartesian lhs;
PmCartesian rhs;
if (!finite_cart(left) || !finite_cart(right) || output == 0) return SIM_POSEMATH_ERR_ARGUMENT;
lhs = to_pm(left);
rhs = to_pm(right);
if (pmCartCartDot(&lhs, &rhs, output) != 0 || !isfinite(*output)) return SIM_POSEMATH_ERR_NONFINITE;
return SIM_POSEMATH_OK;
}
int sim_posemath_line_point(const sim_posemath_cartesian *start, const sim_posemath_cartesian *end,
double fraction, sim_posemath_cartesian *output) {
PmCartesian begin;
PmCartesian finish;
PmCartesian result;
PmCartesian delta;
if (!finite_cart(start) || !finite_cart(end) || output == 0) return SIM_POSEMATH_ERR_ARGUMENT;
if (!isfinite(fraction) || fraction < 0.0 || fraction > 1.0) return SIM_POSEMATH_ERR_RANGE;
begin = to_pm(start);
finish = to_pm(end);
if (pmCartCartSub(&finish, &begin, &delta) != 0 || pmCartScalMult(&delta, fraction, &delta) != 0 || pmCartCartAdd(&begin, &delta, &result) != 0) return SIM_POSEMATH_ERR_DEGENERATE;
from_pm(&result, output);
return SIM_POSEMATH_OK;
}

View File

@@ -0,0 +1,112 @@
#include "sim_protocol.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
typedef struct {
uint32_t state;
uint32_t last_seq;
uint32_t tick;
} protocol_state;
static protocol_state state;
static int has_text(const char *value) {
return value != NULL && value[0] != '\0' && strlen(value) <= 128;
}
static int is_kind(const char *kind, const char *expected) {
return kind != NULL && strcmp(kind, expected) == 0;
}
static int is_side_effect(const char *kind) {
static const char *const commands[] = {
"SESSION_CREATE", "LOAD_PROJECT", "START", "STOP", "RESET",
"LOAD_WORKSPACE", "SIM_STEP", "COMMAND",
"PROGRAM_LOAD", "PROGRAM_RUN", "PROGRAM_PAUSE", "PROGRAM_RESUME",
"PROGRAM_ABORT", "PROGRAM_STEP", "PROGRAM_SEEK", "MDI_EXECUTE",
"JOG_START", "JOG_STOP", "HOME", "SET_MODE", "SET_OVERRIDE",
"SPINDLE_COMMAND", "COOLANT_COMMAND", "TOOL_PREPARE", "TOOL_CHANGE",
"PROBE_CONFIG", "HAL_COMMAND", "HAL_SET", "PLC_START", "PLC_STOP",
"PROJECT_SAVE", "IMPORT", "EXPORT", "CHECKPOINT", "RECOVER",
"SUBSCRIBE", "UNSUBSCRIBE", "CANCEL", NULL,
};
size_t index;
if (!has_text(kind)) return 0;
for (index = 0; commands[index] != NULL; ++index) {
if (is_kind(kind, commands[index])) return 1;
}
return 0;
}
static int state_accepts_kind(uint32_t current, const char *kind) {
if (current == SIM_PROTOCOL_NEW) return is_kind(kind, "HELLO");
if (current == SIM_PROTOCOL_HELLO) return is_kind(kind, "SESSION_ATTACH") || is_kind(kind, "CAPABILITIES");
if (current == SIM_PROTOCOL_ATTACHED) return is_kind(kind, "SESSION_READY");
if (current == SIM_PROTOCOL_READY) return 1;
if (current == SIM_PROTOCOL_PAUSED) {
return is_kind(kind, "PROGRAM_RESUME") || is_kind(kind, "RESUME") ||
is_kind(kind, "PING") || is_kind(kind, "SNAPSHOT_REQUEST") ||
is_kind(kind, "DESTROY");
}
return is_kind(kind, "RESET");
}
void sim_protocol_reset(void) {
memset(&state, 0, sizeof(state));
}
int32_t sim_protocol_accept(uint32_t schema_version, uint32_t seq,
const char *kind, const char *operation_id,
uint32_t simulation_tick) {
if (schema_version != 1U) return SIM_PROTOCOL_ERR_SCHEMA;
if (!has_text(kind)) return SIM_PROTOCOL_ERR_KIND;
if (state.state == SIM_PROTOCOL_DESTROYED && !is_kind(kind, "RESET")) return SIM_PROTOCOL_ERR_STATE;
if (seq == 0U || seq != state.last_seq + 1U) return SIM_PROTOCOL_ERR_SEQUENCE;
if (simulation_tick < state.tick) return SIM_PROTOCOL_ERR_TICK;
if (!state_accepts_kind(state.state, kind)) return SIM_PROTOCOL_ERR_STATE;
if (is_side_effect(kind) && !has_text(operation_id)) return SIM_PROTOCOL_ERR_OPERATION_ID;
state.last_seq = seq;
state.tick = simulation_tick;
if (is_kind(kind, "HELLO")) state.state = SIM_PROTOCOL_HELLO;
/* SESSION_READY is the worker response; the next client command is valid immediately. */
else if (is_kind(kind, "SESSION_ATTACH")) state.state = SIM_PROTOCOL_READY;
else if (is_kind(kind, "SESSION_READY")) state.state = SIM_PROTOCOL_READY;
else if (is_kind(kind, "PROGRAM_PAUSE")) state.state = SIM_PROTOCOL_PAUSED;
else if (is_kind(kind, "PROGRAM_RESUME") || is_kind(kind, "RESUME")) state.state = SIM_PROTOCOL_READY;
else if (is_kind(kind, "DESTROY")) state.state = SIM_PROTOCOL_DESTROYED;
else if (is_kind(kind, "RESET")) sim_protocol_reset();
return SIM_PROTOCOL_OK;
}
int32_t sim_protocol_pause(void) {
if (state.state != SIM_PROTOCOL_READY) return SIM_PROTOCOL_ERR_STATE;
state.state = SIM_PROTOCOL_PAUSED;
return SIM_PROTOCOL_OK;
}
int32_t sim_protocol_resume(void) {
if (state.state != SIM_PROTOCOL_PAUSED) return SIM_PROTOCOL_ERR_STATE;
state.state = SIM_PROTOCOL_READY;
return SIM_PROTOCOL_OK;
}
int32_t sim_protocol_state_value(void) {
return (int32_t)state.state;
}
uint32_t sim_protocol_last_seq(void) {
return state.last_seq;
}
uint32_t sim_protocol_tick(void) {
return state.tick;
}
int32_t sim_protocol_set_tick(uint32_t simulation_tick) {
if (simulation_tick < state.tick) return SIM_PROTOCOL_ERR_TICK;
state.tick = simulation_tick;
return SIM_PROTOCOL_OK;
}

View File

@@ -0,0 +1,26 @@
#include "sim_api.h"
#include <assert.h>
#include <string.h>
#include <stdint.h>
int main(void) {
uint8_t snapshot[sizeof(uint64_t)] = {0};
uint8_t state[32] = {0};
assert(sim_step(1) == SIM_ERR_NOT_CREATED);
assert(sim_create(NULL, 0) == SIM_OK);
assert(sim_load_workspace("/workspace") == SIM_OK);
assert(sim_step(7) == SIM_OK);
assert(sim_read_snapshot(snapshot, sizeof(snapshot)) == sizeof(snapshot));
uint64_t tick = 0;
memcpy(&tick, snapshot, sizeof(tick));
assert(tick == 7);
assert(sim_export_state(state, sizeof(state)) > 0);
assert(sim_step(3) == SIM_OK);
assert(sim_import_state(state, sizeof(state)) == SIM_OK);
assert(sim_read_snapshot(snapshot, sizeof(snapshot)) == sizeof(snapshot));
memcpy(&tick, snapshot, sizeof(tick));
assert(tick == 7);
sim_destroy();
return 0;
}

View File

@@ -0,0 +1,28 @@
#include "sim_event.h"
#include <assert.h>
#include <string.h>
int main(void) {
uint8_t record[sizeof(sim_event_record)] = {0};
uint8_t exported[16384] = {0};
const uint8_t payload[] = {'o', 'k'};
sim_event_reset();
assert(sim_event_append(2, 0, "bad", payload, sizeof(payload)) == SIM_EVENT_ERR_SEQUENCE);
assert(sim_event_append(1, 2, "STEP", payload, sizeof(payload)) == SIM_EVENT_OK);
assert(sim_event_append(2, 1, "BAD_TICK", NULL, 0) == SIM_EVENT_ERR_TICK);
assert(sim_event_read(0, NULL, 0) == sizeof(sim_event_record));
assert(sim_event_read(0, record, sizeof(record)) == sizeof(record));
assert(((sim_event_record *)record)->seq == 1);
assert(sim_event_count() == 1);
assert(sim_event_cursor() == 1);
uint32_t export_size = sim_event_export(NULL, 0);
assert(export_size > 0 && export_size < sizeof(exported));
assert(sim_event_export(exported, sizeof(exported)) == export_size);
sim_event_reset();
assert(sim_event_import(exported, export_size) == SIM_EVENT_OK);
assert(sim_event_count() == 1);
assert(sim_event_import(exported, export_size - 1) == SIM_EVENT_ERR_ARGUMENT);
return 0;
}

View File

@@ -0,0 +1,29 @@
#include "sim_hal.h"
#include <assert.h>
int main(void) {
sim_hal_value bit = {SIM_HAL_BIT, {.bit = 1}};
sim_hal_value wrong = {SIM_HAL_S32, {.s32 = 1}};
sim_hal_value out = {0};
sim_hal_reset();
assert(sim_hal_register_pin("source", SIM_HAL_BIT) == SIM_HAL_OK);
assert(sim_hal_register_pin("target", SIM_HAL_BIT) == SIM_HAL_OK);
assert(sim_hal_register_pin("source", SIM_HAL_BIT) == SIM_HAL_ERR_DUPLICATE);
assert(sim_hal_set_pin("source", bit) == SIM_HAL_OK);
assert(sim_hal_set_pin("source", wrong) == SIM_HAL_ERR_TYPE);
assert(sim_hal_connect("source", "target") == SIM_HAL_OK);
assert(sim_hal_get_pin("target", &out) == SIM_HAL_OK && out.value.bit == 1);
assert(sim_hal_register_thread("servo", 2) == SIM_HAL_OK);
assert(sim_hal_register_thread("servo", 2) == SIM_HAL_ERR_DUPLICATE);
assert(sim_hal_register_function("servo", "update") == SIM_HAL_OK);
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);
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);
assert(sim_hal_register_thread("invalid", 0) == SIM_HAL_ERR_RANGE);
return 0;
}

View File

@@ -0,0 +1,23 @@
#include "sim_posemath.h"
#include <assert.h>
#include <math.h>
int main(void) {
sim_posemath_cartesian input = {3.0, 4.0, 0.0};
sim_posemath_cartesian output = {0};
sim_posemath_cartesian end = {10.0, 10.0, 10.0};
double dot = 0;
assert(sim_posemath_normalize(&input, &output) == SIM_POSEMATH_OK);
assert(fabs(output.x - 0.6) < 1e-12 && fabs(output.y - 0.8) < 1e-12);
assert(sim_posemath_dot(&input, &end, &dot) == SIM_POSEMATH_OK && dot == 70.0);
assert(sim_posemath_line_point(&input, &end, 0.5, &output) == SIM_POSEMATH_OK);
assert(output.x == 6.5 && output.y == 7.0 && output.z == 5.0);
assert(sim_posemath_line_point(&input, &end, 1.1, &output) == SIM_POSEMATH_ERR_RANGE);
input.x = NAN;
assert(sim_posemath_normalize(&input, &output) == SIM_POSEMATH_ERR_ARGUMENT);
input.x = 0.0; input.y = 0.0; input.z = 0.0;
assert(sim_posemath_normalize(&input, &output) == SIM_POSEMATH_ERR_DEGENERATE);
return 0;
}

View File

@@ -0,0 +1,25 @@
#include "sim_protocol.h"
#include <assert.h>
int main(void) {
sim_protocol_reset();
assert(sim_protocol_state_value() == SIM_PROTOCOL_NEW);
assert(sim_protocol_accept(1, 1, "HELLO", NULL, 0) == SIM_PROTOCOL_OK);
assert(sim_protocol_accept(1, 3, "SESSION_ATTACH", NULL, 0) == SIM_PROTOCOL_ERR_SEQUENCE);
assert(sim_protocol_accept(1, 2, "SESSION_ATTACH", NULL, 0) == SIM_PROTOCOL_OK);
assert(sim_protocol_state_value() == SIM_PROTOCOL_READY);
assert(sim_protocol_accept(1, 3, "MDI_EXECUTE", NULL, 0) == SIM_PROTOCOL_ERR_OPERATION_ID);
assert(sim_protocol_accept(1, 3, "MDI_EXECUTE", "op-3", 2) == SIM_PROTOCOL_OK);
assert(sim_protocol_accept(1, 4, "PROGRAM_PAUSE", "op-4", 2) == SIM_PROTOCOL_OK);
assert(sim_protocol_accept(1, 5, "MDI_EXECUTE", "op-5", 2) == SIM_PROTOCOL_ERR_STATE);
assert(sim_protocol_accept(1, 5, "PROGRAM_RESUME", "op-5", 2) == SIM_PROTOCOL_OK);
assert(sim_protocol_accept(2, 6, "PING", NULL, 2) == SIM_PROTOCOL_ERR_SCHEMA);
assert(sim_protocol_accept(1, 6, "PING", NULL, 1) == SIM_PROTOCOL_ERR_TICK);
assert(sim_protocol_accept(1, 6, "DESTROY", NULL, 2) == SIM_PROTOCOL_OK);
assert(sim_protocol_accept(1, 7, "PING", NULL, 2) == SIM_PROTOCOL_ERR_STATE);
assert(sim_protocol_accept(1, 8, "RESET", "reset-1", 0) == SIM_PROTOCOL_OK);
assert(sim_protocol_state_value() == SIM_PROTOCOL_NEW);
assert(sim_protocol_set_tick(4) == SIM_PROTOCOL_ERR_TICK);
return 0;
}