Implement file system API and add shutdown functionality for desktop builds
This commit is contained in:
@@ -31,6 +31,7 @@ else()
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
message(STATUS "[Nova64] Building as Desktop Shared Library...")
|
||||
add_compile_definitions(NOVA64_SHUTDOWN_AVAILABLE) # Enable shutdown functionality for desktop builds
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# FreeRTOS Configuration
|
||||
|
||||
+27
-12
@@ -1,9 +1,10 @@
|
||||
#ifndef NOVA64_CORE_H
|
||||
#define NOVA64_CORE_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
|
||||
// Cross-platform DLL export/import visibility macros
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
@@ -42,16 +43,27 @@ typedef void (*nova64_audio_output_cb_t)(const int16_t *samples,
|
||||
uint32_t count);
|
||||
|
||||
/**
|
||||
* @brief Callback to read sectors from the SD card.
|
||||
* @param sector_idx The zero-based sector index to read.
|
||||
* @param destination_buffer Pointer to memory where sector data should be
|
||||
* copied.
|
||||
* @param sector_count Number of 512-byte sectors to read.
|
||||
* @return True on success, false on read error.
|
||||
* @brief Callback to read data from the SD card.
|
||||
* @param slot_idx Zero-based cartridge slot index.
|
||||
* @param file_path Null-terminated path to the file to read.
|
||||
* @param destination_buffer Pointer to memory where file data should be copied.
|
||||
* @param destination_length Maximum number of bytes available in the
|
||||
* destination buffer.
|
||||
* @return Number of bytes read on success, negative value on read error.
|
||||
*/
|
||||
typedef bool (*nova64_sd_read_sector_cb_t)(uint32_t sector_idx,
|
||||
uint8_t *destination_buffer,
|
||||
uint32_t sector_count);
|
||||
typedef int32_t (*nova64_sd_read_file_cb_t)(uint32_t slot_idx,
|
||||
const char *file_path,
|
||||
uint8_t *destination_buffer,
|
||||
uint32_t destination_length);
|
||||
|
||||
/**
|
||||
* @brief Callback to retrieve the size of a file on the SD card.
|
||||
* @param slot_idx Zero-based cartridge slot index.
|
||||
* @param file_path Null-terminated path to the file.
|
||||
* @return File size in bytes on success, negative value on error.
|
||||
*/
|
||||
typedef int32_t (*nova64_sd_get_file_size_cb_t)(uint32_t slot_idx,
|
||||
const char *file_path);
|
||||
|
||||
/**
|
||||
* @brief Callback triggered when a cartridge state change occurs (e.g.
|
||||
@@ -60,7 +72,7 @@ typedef bool (*nova64_sd_read_sector_cb_t)(uint32_t sector_idx,
|
||||
* @param is_inserted True if a cartridge is detected, false if ejected.
|
||||
*/
|
||||
typedef void (*nova64_cartridge_status_cb_t)(uint32_t slot_idx,
|
||||
bool is_inserted);
|
||||
bool is_inserted);
|
||||
|
||||
/**
|
||||
* @brief Callback for core log output.
|
||||
@@ -79,7 +91,8 @@ typedef void (*nova64_log_cb_t)(const char *message);
|
||||
typedef struct {
|
||||
nova64_display_flush_cb_t display_flush;
|
||||
nova64_audio_output_cb_t audio_output;
|
||||
nova64_sd_read_sector_cb_t sd_read_sector;
|
||||
nova64_sd_read_file_cb_t sd_read_file;
|
||||
nova64_sd_get_file_size_cb_t sd_get_file_size;
|
||||
nova64_cartridge_status_cb_t cartridge_status;
|
||||
nova64_log_cb_t log_message;
|
||||
} nova64_io_interface_t;
|
||||
@@ -104,11 +117,13 @@ NOVA64_API bool nova64_init(const nova64_io_interface_t *io_interface);
|
||||
*/
|
||||
NOVA64_API int32_t nova64_main();
|
||||
|
||||
#if defined(NOVA64_SHUTDOWN_AVAILABLE)
|
||||
/**
|
||||
* @brief Requests shutdown of the FreeRTOS scheduler.
|
||||
* @return True if the shutdown request was issued, false otherwise.
|
||||
*/
|
||||
NOVA64_API bool nova64_shutdown(void);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Retrieves the engine version integer.
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef NOVA64_FS_H
|
||||
#define NOVA64_FS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* ========================================================================== */
|
||||
/* File System API */
|
||||
/* ========================================================================== */
|
||||
|
||||
/**
|
||||
* @brief Reads a file from the filesystem into a buffer.
|
||||
* Supports drive identifiers: "SYS:" (internal system store),
|
||||
* "EXA:" (cartridge slot 0), "EXB:" (cartridge slot 1), etc.
|
||||
* @param file_path Null-terminated file path with drive identifier
|
||||
* (e.g., "SYS:/init.wasm" or "EXA:/game.wasm").
|
||||
* @param buffer Pointer to destination buffer where file contents will be
|
||||
* copied.
|
||||
* @param buffer_size Size in bytes of the destination buffer.
|
||||
* @return True if file was successfully read into buffer, false on error
|
||||
* (file not found, read error, or buffer too small).
|
||||
* @note Uses the sd_read_sector callback from nova64_io_interface_t.
|
||||
* The callback must be registered via nova64_init() before calling
|
||||
* this function.
|
||||
*/
|
||||
bool nova64_read_file_to_buffer(const char *file_path, uint8_t *buffer,
|
||||
size_t buffer_size);
|
||||
|
||||
/**
|
||||
* @brief Gets the size of a file in the filesystem.
|
||||
* @param file_path Null-terminated file path with drive identifier
|
||||
* (e.g., "SYS:/init.wasm" or "EXA:/game.wasm").
|
||||
* @return Size of the file in bytes on success, or -1 on error
|
||||
* (file not found, read error, etc.).
|
||||
*/
|
||||
int32_t nova64_get_file_size(const char *file_path);
|
||||
|
||||
#endif // NOVA64_FS_H
|
||||
@@ -1,6 +1,10 @@
|
||||
#ifndef NOVA64_INTERNAL_H
|
||||
#define NOVA64_INTERNAL_H
|
||||
|
||||
#include "nova64_core.h"
|
||||
|
||||
void nova64_log(const char *format, ...);
|
||||
|
||||
const nova64_io_interface_t *nova64_get_io_interface(void);
|
||||
|
||||
#endif // NOVA64_INTERNAL_H
|
||||
@@ -4,12 +4,6 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
wasm_runner_role_main = 0,
|
||||
wasm_runner_role_status,
|
||||
wasm_runner_role_background,
|
||||
} wasm_runner_role_t;
|
||||
|
||||
typedef struct wasm_runner_instance wasm_runner_instance_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
+17
-2
@@ -1,13 +1,12 @@
|
||||
#include "nova64_core.h"
|
||||
#include "nova64_internal.h"
|
||||
#include "FreeRTOS.h"
|
||||
#include "nova64_internal.h"
|
||||
#include "queue.h"
|
||||
#include "task.h"
|
||||
#include "wasm_runner.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
#if defined(ESP_PLATFORM)
|
||||
#include "esp_pm.h"
|
||||
#include "esp_rom_sys.h"
|
||||
@@ -26,8 +25,11 @@ static const nova64_io_interface_t *g_io_interface = NULL;
|
||||
static QueueHandle_t event_queue = NULL;
|
||||
static TaskHandle_t boot_task_handle = NULL;
|
||||
static TaskHandle_t launcher_task_handle = NULL;
|
||||
|
||||
#if defined(NOVA64_SHUTDOWN_AVAILABLE)
|
||||
static TaskHandle_t shutdown_task_handle = NULL;
|
||||
static volatile bool shutdown_requested = false;
|
||||
#endif
|
||||
|
||||
static const char *const kInternalLauncherPath = "SYS:/launcher.wasm";
|
||||
|
||||
@@ -83,6 +85,7 @@ static bool start_launcher_task(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(NOVA64_SHUTDOWN_AVAILABLE)
|
||||
static void nova64_shutdown_task(void *params) {
|
||||
(void)params;
|
||||
|
||||
@@ -110,6 +113,7 @@ static bool start_shutdown_task(void) {
|
||||
nova64_log("Shutdown task created successfully.\n");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void nova64_boot_task(void *params) {
|
||||
(void)params;
|
||||
@@ -137,6 +141,10 @@ static void nova64_boot_task(void *params) {
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
const nova64_io_interface_t *nova64_get_io_interface(void) {
|
||||
return g_io_interface;
|
||||
}
|
||||
|
||||
NOVA64_API bool nova64_init(const nova64_io_interface_t *io_interface) {
|
||||
if (is_initialized || io_interface == NULL) {
|
||||
return false;
|
||||
@@ -144,7 +152,10 @@ NOVA64_API bool nova64_init(const nova64_io_interface_t *io_interface) {
|
||||
|
||||
g_io_interface_storage = *io_interface;
|
||||
g_io_interface = &g_io_interface_storage;
|
||||
|
||||
#if defined(NOVA64_SHUTDOWN_AVAILABLE)
|
||||
shutdown_requested = false;
|
||||
#endif
|
||||
|
||||
nova64_log("System Initializing...\n");
|
||||
|
||||
@@ -154,9 +165,11 @@ NOVA64_API bool nova64_init(const nova64_io_interface_t *io_interface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(NOVA64_SHUTDOWN_AVAILABLE)
|
||||
if (!start_shutdown_task()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
nova64_log("Starting boot process...\n");
|
||||
BaseType_t created =
|
||||
@@ -186,6 +199,7 @@ NOVA64_API int32_t nova64_main() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if defined(NOVA64_SHUTDOWN_AVAILABLE)
|
||||
NOVA64_API bool nova64_shutdown(void) {
|
||||
if (!is_initialized) {
|
||||
return false;
|
||||
@@ -195,6 +209,7 @@ NOVA64_API bool nova64_shutdown(void) {
|
||||
shutdown_requested = true;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
NOVA64_API uint32_t nova64_get_version(void) {
|
||||
return 0x010000; // v1.0.0
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "nova64_fs.h"
|
||||
#include "nova64_core.h"
|
||||
#include "nova64_internal.h"
|
||||
|
||||
/* Helper for parsing the drive prefix from a path like "SYS:/file" or "EXA:/file".
|
||||
* Returns the drive index on success, or -1 on failure. */
|
||||
static int parse_drive_index(const char *path)
|
||||
{
|
||||
if (!path || path[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strncmp(path, "SYS:/", 5) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strncmp(path, "EX", 2) == 0 && path[3] == ':' && path[4] == '/') {
|
||||
char drive_letter = path[2];
|
||||
if (drive_letter >= 'A' && drive_letter <= 'Z') {
|
||||
return drive_letter - ('A' - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool nova64_read_file_to_buffer(const char *file_path,
|
||||
uint8_t *buffer,
|
||||
size_t buffer_size)
|
||||
{
|
||||
if (!file_path || !buffer || buffer_size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int drive_index = parse_drive_index(file_path);
|
||||
if (drive_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *relative_path = file_path + 5;
|
||||
if (*relative_path == '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nova64_io_interface_t *io = nova64_get_io_interface();
|
||||
if (!io || !io->sd_read_file || !io->sd_get_file_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Get the file size first */
|
||||
int32_t file_size = io->sd_get_file_size(drive_index, relative_path);
|
||||
if (file_size < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Determine how much to read */
|
||||
size_t bytes_to_read = (size_t)file_size;
|
||||
if (bytes_to_read > buffer_size) {
|
||||
bytes_to_read = buffer_size;
|
||||
}
|
||||
|
||||
/* Read the entire file into the buffer */
|
||||
int32_t bytes_read = io->sd_read_file(drive_index, relative_path, buffer, bytes_to_read);
|
||||
if (bytes_read < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bytes_read > 0;
|
||||
}
|
||||
|
||||
|
||||
int32_t nova64_get_file_size(const char *file_path)
|
||||
{
|
||||
if (!file_path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int drive_index = parse_drive_index(file_path);
|
||||
if (drive_index < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *relative_path = file_path + 5;
|
||||
if (*relative_path == '\0') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const nova64_io_interface_t *io = nova64_get_io_interface();
|
||||
if (!io || !io->sd_get_file_size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return io->sd_get_file_size(drive_index, relative_path);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "wasm_runner.h"
|
||||
#include "nova64_fs.h"
|
||||
#include "nova64_internal.h"
|
||||
#include "wasm_export.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
struct wasm_runner_instance {
|
||||
void *native_instance;
|
||||
@@ -9,8 +11,40 @@ struct wasm_runner_instance {
|
||||
wasm_runner_instance_t *next;
|
||||
};
|
||||
|
||||
char error_buf[128];
|
||||
uint32_t stack_size = 8092, heap_size = 8092;
|
||||
|
||||
bool wasm_runner_initialize(void) {
|
||||
|
||||
wasm_runtime_init();
|
||||
nova64_log("WASM runtime initialized successfully.\n");
|
||||
|
||||
const char *module_file = "SYS:/init.wasm";
|
||||
int32_t file_size = nova64_get_file_size(module_file);
|
||||
if (file_size <= 0) {
|
||||
nova64_log("Failed to determine WASM module size.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char *buffer = malloc((size_t)file_size);
|
||||
if (!buffer) {
|
||||
nova64_log("Failed to allocate WASM module buffer.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!nova64_read_file_to_buffer(module_file, buffer, (size_t)file_size)) {
|
||||
nova64_log("Failed to read WASM module into buffer.\n");
|
||||
free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
wasm_module_t module = wasm_runtime_load(buffer, (uint32_t)file_size,
|
||||
error_buf, sizeof(error_buf));
|
||||
|
||||
wasm_module_inst_t module_inst = wasm_runtime_instantiate(
|
||||
module, stack_size, heap_size, error_buf, sizeof(error_buf));
|
||||
|
||||
wasm_exec_env_t exec_env = wasm_runtime_create_exec_env(module_inst, stack_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@ internal static class Nova64Core
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void AudioOutputDelegate(IntPtr samples, uint count);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate bool SdReadSectorDelegate(uint sectorIdx, IntPtr destinationBuffer, uint sectorCount);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate int SdReadFileDelegate(uint slotIdx, string filePath, IntPtr destinationBuffer, uint destinationLength);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
public delegate int SdGetFileSizeDelegate(uint slotIdx, string filePath);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void CartridgeStatusDelegate(uint slotIdx, bool isInserted);
|
||||
@@ -24,7 +27,8 @@ internal static class Nova64Core
|
||||
{
|
||||
public IntPtr display_flush;
|
||||
public IntPtr audio_output;
|
||||
public IntPtr sd_read_sector;
|
||||
public IntPtr sd_read_file;
|
||||
public IntPtr sd_get_file_size;
|
||||
public IntPtr cartridge_status;
|
||||
public IntPtr log_message;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,14 @@
|
||||
<NativeLibDir>$(NativeLibDir)</NativeLibDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Zio" Version="0.24.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="sd\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(NativeLibDir)' != ''">
|
||||
<Content Include="$(NativeLibDir)\nova64_core.dll" Condition="Exists('$(NativeLibDir)\nova64_core.dll')" CopyToOutputDirectory="IfDifferent" />
|
||||
<Content Include="$(NativeLibDir)\libnova64_core.so" Condition="Exists('$(NativeLibDir)\libnova64_core.so')" CopyToOutputDirectory="IfDifferent" />
|
||||
|
||||
+68
-2
@@ -1,12 +1,77 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Zio;
|
||||
using Zio.FileSystems;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static readonly IFileSystem SdFileSystem = new PhysicalFileSystem();
|
||||
private static readonly UPath SdRoot = new UPath(Path.Combine(AppContext.BaseDirectory, "sd").Replace('\\', '/').Replace("C:", "/mnt/c"));
|
||||
|
||||
private static UPath GetSlotFilePath(uint slotIdx, string filePath)
|
||||
{
|
||||
var normalizedPath = (filePath ?? string.Empty).Replace('\\', '/').TrimStart('/');
|
||||
return SdRoot / $"slot{slotIdx}" / normalizedPath;
|
||||
}
|
||||
|
||||
private static readonly Nova64Core.DisplayFlushDelegate DisplayFlush = (buffer, width, height) => { };
|
||||
private static readonly Nova64Core.AudioOutputDelegate AudioOutput = (samples, count) => { };
|
||||
private static readonly Nova64Core.SdReadSectorDelegate SdReadSector = (sectorIdx, destinationBuffer, sectorCount) => false;
|
||||
private static readonly Nova64Core.SdReadFileDelegate SdReadFile = (slotIdx, filePath, destinationBuffer, destinationLength) =>
|
||||
{
|
||||
var path = GetSlotFilePath(slotIdx, filePath);
|
||||
Console.WriteLine($"[Core] SD read request: slot {slotIdx}, file '{filePath}', resolved path '{path}' destination length {destinationLength}");
|
||||
|
||||
if (destinationBuffer == IntPtr.Zero)
|
||||
{
|
||||
Console.WriteLine("[Core] SD read request failed: destination buffer is null.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!SdFileSystem.FileExists(path))
|
||||
{
|
||||
Console.WriteLine($"[Core] SD read request failed: file not found '{path}'");
|
||||
return -1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var stream = SdFileSystem.OpenFile(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var bytesToRead = (int)Math.Min(destinationLength, stream.Length);
|
||||
var buffer = new byte[bytesToRead];
|
||||
var bytesRead = stream.Read(buffer, 0, bytesToRead);
|
||||
Marshal.Copy(buffer, 0, destinationBuffer, bytesRead);
|
||||
return bytesRead;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Core] SD read request failed: {ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
private static readonly Nova64Core.SdGetFileSizeDelegate SdGetFileSize = (slotIdx, filePath) =>
|
||||
{
|
||||
var path = GetSlotFilePath(slotIdx, filePath);
|
||||
Console.WriteLine($"[Core] SD get file size request: slot {slotIdx}, file '{filePath}', resolved path '{path}'");
|
||||
|
||||
if (!SdFileSystem.FileExists(path))
|
||||
{
|
||||
Console.WriteLine($"[Core] SD get file size failed: file not found '{path}'");
|
||||
return -1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var length = SdFileSystem.GetFileLength(path);
|
||||
return length > int.MaxValue ? -1 : (int)length;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[Core] SD get file size failed: {ex.Message}");
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
private static readonly Nova64Core.CartridgeStatusDelegate CartridgeStatus = (slotIdx, isInserted) => { };
|
||||
private static readonly Nova64Core.LogMessageDelegate LogMessage = (message) =>
|
||||
{
|
||||
@@ -51,7 +116,8 @@ internal static class Program
|
||||
{
|
||||
display_flush = Marshal.GetFunctionPointerForDelegate(DisplayFlush),
|
||||
audio_output = Marshal.GetFunctionPointerForDelegate(AudioOutput),
|
||||
sd_read_sector = Marshal.GetFunctionPointerForDelegate(SdReadSector),
|
||||
sd_read_file = Marshal.GetFunctionPointerForDelegate(SdReadFile),
|
||||
sd_get_file_size = Marshal.GetFunctionPointerForDelegate(SdGetFileSize),
|
||||
cartridge_status = Marshal.GetFunctionPointerForDelegate(CartridgeStatus),
|
||||
log_message = Marshal.GetFunctionPointerForDelegate(LogMessage)
|
||||
};
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
// Declare a main function, this is the entrypoint into our go module
|
||||
// That will be run. In our example, we won't need this
|
||||
func main() {}
|
||||
|
||||
// This exports an add function.
|
||||
// It takes in two 32-bit integer values
|
||||
// And returns a 32-bit integer value.
|
||||
// To make this function callable from JavaScript,
|
||||
// we need to add the: "export add" comment above the function
|
||||
//export add
|
||||
func add(x int, y int) int {
|
||||
return x + y
|
||||
}
|
||||
Reference in New Issue
Block a user