playdate support

This commit is contained in:
2025-12-07 04:04:11 -06:00
parent ce5949e0ee
commit c24a5079cb
21 changed files with 1645 additions and 4614 deletions

View File

@@ -1,6 +1,3 @@
sdl_video = "main"
[dependencies]
extramath = "gitea.pockle.world/john/extramath@master"
[system]
ar_timer = 60
actor_memory = 0
@@ -16,4 +13,18 @@ main = true
[actors.prosperon]
main = true
[actors.accio]
main = true
main = true
[compilation]
CFLAGS = "-Wno-incompatible-pointer-types -Wno-missing-braces -Wno-strict-prototypes -Wno-unused-function -Wno-int-conversion"
LDFLAGS = "-lstdc++ -lm"
[compilation.macOS]
CFLAGS = "-x objective-c"
LDFLAGS = "-framework CoreFoundation -framework CFNetwork"
[compilation.playdate]
CFLAGS = "-DMINIZ_NO_TIME -DTARGET_EXTENSION -DTARGET_PLAYDATE -I$PLAYDATE_SDK_PATH/C_API"
[compilation.windows]
LDFLAGS = "-lbcrypt -lwinhttp -static-libgcc -static-libstdc++"

View File

@@ -8,32 +8,15 @@ libtype = get_option('default_library')
link = []
src = []
add_project_arguments('-Wno-int-conversion', language: ['c'])
git_tag_cmd = run_command('git', 'describe', '--tags', '--abbrev=0', check: false)
cell_version = 'unknown'
if git_tag_cmd.returncode() == 0
cell_version = git_tag_cmd.stdout().strip()
endif
git_commit_cmd = run_command('git', 'rev-parse', '--short', 'HEAD', check: false)
cell_commit = 'unknown'
if git_commit_cmd.returncode() == 0
cell_commit = git_commit_cmd.stdout().strip()
endif
# Important: pass the definitions without double-escaping quotes
add_project_arguments(
'-DCELL_VERSION="' + cell_version + '"',
'-DCELL_COMMIT="' + cell_commit + '"',
language : 'c'
'-Wno-incompatible-pointer-types',
'-Wno-missing-braces',
'-Wno-strict-prototypes',
'-Wno-unused-function',
'-Wno-int-conversion',
language: 'c'
)
add_project_arguments('-Wno-incompatible-pointer-types', language: 'c')
add_project_arguments('-Wno-narrowing', language: 'cpp')
add_project_arguments('-Wno-missing-braces', language:'c')
add_project_arguments('-Wno-strict-prototypes', language:'c')
add_project_arguments('-Wno-unused-function', language: 'c')
deps = []
@@ -52,24 +35,6 @@ if host_machine.system() == 'playdate'
add_project_arguments('-DTARGET_PLAYDATE', language: 'c')
endif
cmake = import('cmake')
cc = meson.get_compiler('c')
if host_machine.system() == 'linux'
deps += cc.find_library('asound', required:true)
deps += [dependency('x11'), dependency('xi'), dependency('xcursor'), dependency('egl'), dependency('gl')]
endif
if host_machine.system() == 'windows'
deps += cc.find_library('bcrypt')
deps += cc.find_library('winhttp')
link += ['-static', '-static-libgcc', '-static-libstdc++']
add_project_link_arguments('-static-libgcc', '-static-libstdc++', language: ['c', 'cpp'])
endif
#link += '-rdynamic'
link_args = link
sources = []
src += [ # core
@@ -79,18 +44,12 @@ src += [ # core
'wildmatch.c',
'qjs_actor.c',
'qjs_wota.c',
'miniz.c'
'miniz.c',
'quickjs.c',
'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c'
]
if host_machine.system() == 'playdate'
src += ['scheduler_single.c']
elif get_option('single_threaded')
src += ['scheduler_single.c']
else
src += ['scheduler_threaded.c']
endif
src += ['quickjs.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c']
src += ['scheduler.c']
scripts = [
'nota.c',
@@ -118,12 +77,6 @@ foreach file: scripts
sources += files(full_path)
endforeach
if host_machine.system() == 'playdate'
sources += files('source/fd_playdate.c')
else
sources += files('scripts/fd.c')
endif
srceng = 'source'
includes = [srceng]
@@ -201,18 +154,3 @@ cell = custom_target('cell',
install_headers('source/cell.h')
install_headers('source/quickjs.h')
install_headers('source/wota.h')
tests = [
'spawn_actor',
'empty',
'nota',
'wota',
'portalspawner',
'overling',
'send',
'delay'
]
foreach file : tests
test(file, cell, args:['tests/' + file])
endforeach

319
scripts/build.ce Normal file
View File

@@ -0,0 +1,319 @@
// cell build [--target <target>] - Build a static cell binary for a project
// Collects all C files from cell source, scripts, and project packages,
// compiles them, and links into a single static executable.
var build = use('build')
var shop = use('shop')
var fd = use('fd')
var os = use('os')
var targets = [
"arm64-macos",
"x86_64-macos",
"x86_64-linux",
"arm64-linux",
"windows",
"playdate"
]
// Parse arguments
var target = null
for (var i = 0; i < args.length; i++) {
if (args[i] == '--target' || args[i] == '-t') {
if (i + 1 < args.length) {
target = args[i + 1]
i++
} else {
log.error("--target requires an argument")
log.console("Available targets: " + targets.join(', '))
$_.stop()
}
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell build [--target <target>]")
log.console("Build a static cell binary for the current project.")
log.console("")
log.console("Options:")
log.console(" --target, -t <target> Cross-compile for target platform")
log.console("")
log.console("Available targets: " + targets.join(', '))
$_.stop()
} else if (args[i] == '--list-targets') {
log.console("Available targets:")
for (var t = 0; t < targets.length; t++) {
log.console(" " + targets[t])
}
$_.stop()
}
}
// Resolve target
target = build.resolve_target(target)
if (target) {
log.console("Building for target: " + target)
} else {
log.console("Building for host platform")
}
// Find cell package - it should be at ~/work/cell or a dependency
var cell_dir = null
// First check if we're in the cell directory itself
if (fd.is_file('source/cell.c') && fd.is_file('source/quickjs.c')) {
cell_dir = '.'
log.console("Building from cell source directory")
} else {
// Check for cell as a local path dependency or linked package
var config = shop.load_config()
if (config && config.dependencies && config.dependencies.cell) {
var pkg = config.dependencies.cell
var parsed = shop.parse_package(pkg)
var pkg_dir = '.cell/modules/' + parsed.path
if (fd.is_file(pkg_dir + '/source/cell.c')) {
cell_dir = pkg_dir
log.console("Using cell from dependency: " + pkg_dir)
}
}
// Fallback: try ~/work/cell
if (!cell_dir) {
var home_cell = os.getenv('HOME') + '/work/cell'
if (fd.is_file(home_cell + '/source/cell.c')) {
cell_dir = home_cell
log.console("Using cell from: " + cell_dir)
}
}
}
if (!cell_dir) {
log.error("Could not find cell source. Add cell as a dependency or run from cell directory.")
$_.stop()
}
// Collect all C files from cell
var source_files = build.list_files(cell_dir + '/source')
var script_files = build.list_files(cell_dir + '/scripts')
// Prefix with directory
var all_files = []
for (var i = 0; i < source_files.length; i++) {
all_files.push('source/' + source_files[i])
}
for (var i = 0; i < script_files.length; i++) {
all_files.push('scripts/' + script_files[i])
}
// Select C files for target
var c_files = build.select_c_files(all_files, target)
// Debug: show which files were selected
if (target == 'playdate') {
for (var i = 0; i < c_files.length; i++) {
if (c_files[i].indexOf('fd') >= 0 || c_files[i].indexOf('main') >= 0) {
log.console(" Selected: " + c_files[i])
}
}
}
log.console("Found " + c_files.length + " C files to compile")
// Get build directory
var build_dir = build.get_build_dir(target)
build.ensure_dir(build_dir)
// Load cell config for platform-specific flags
var cell_config = build.load_config(cell_dir)
var target_system = build.get_target_system(target)
var platform = target_system || os.platform()
var cflags = build.get_flags(cell_config, platform, 'CFLAGS')
var ldflags = build.get_flags(cell_config, platform, 'LDFLAGS')
// Compile options
var compile_options = {
target: target,
cflags: cflags,
includes: [cell_dir + '/source'],
defines: {}
}
// Add target-specific defines
if (target == 'playdate') {
compile_options.defines.TARGET_PLAYDATE = true
}
// Compile all C files
var objects = []
for (var i = 0; i < c_files.length; i++) {
var src = cell_dir + '/' + c_files[i]
var obj = build_dir + '/' + c_files[i] + '.o'
// Check if recompilation needed
var needs_compile = true
if (fd.is_file(obj)) {
var src_stat = fd.stat(src)
var obj_stat = fd.stat(obj)
if (src_stat && obj_stat && src_stat.mtime <= obj_stat.mtime) {
needs_compile = false
}
}
if (needs_compile) {
var result = build.compile_file(src, obj, compile_options)
if (!result) {
log.error("Build failed")
$_.stop()
}
}
objects.push(obj)
}
// Collect C files from project packages
var packages = shop.list_packages()
for (var p = 0; p < packages.length; p++) {
var pkg = packages[p]
var parsed = shop.parse_package(pkg)
var pkg_dir = '.cell/modules/' + parsed.path
if (!fd.is_dir(pkg_dir)) continue
var pkg_files = build.list_files(pkg_dir)
var pkg_c_files = build.select_c_files(pkg_files, target)
var pkg_config = build.load_config(pkg_dir)
var pkg_ldflags = build.get_flags(pkg_config, platform, 'LDFLAGS')
if (pkg_ldflags) {
if (ldflags != '') ldflags += ' '
ldflags += pkg_ldflags
}
if (pkg_c_files.length > 0) {
log.console("Compiling " + text(pkg_c_files.length) + " C files from " + parsed.path)
var pkg_cflags = build.get_flags(pkg_config, platform, 'CFLAGS')
// Create symbol prefix for package
var use_prefix = 'js_' + parsed.path.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_') + '_'
for (var f = 0; f < pkg_c_files.length; f++) {
var src = pkg_dir + '/' + pkg_c_files[f]
var obj = build_dir + '/packages/' + parsed.path + '/' + pkg_c_files[f] + '.o'
var safe_name = pkg_c_files[f].substring(0, pkg_c_files[f].lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_')
var use_name = use_prefix + safe_name + '_use'
var pkg_options = {
target: target,
cflags: pkg_cflags,
includes: [cell_dir + '/source', pkg_dir],
defines: { CELL_USE_NAME: use_name },
module_dir: pkg_dir
}
var needs_compile = true
if (fd.is_file(obj)) {
var src_stat = fd.stat(src)
var obj_stat = fd.stat(obj)
if (src_stat && obj_stat && src_stat.mtime <= obj_stat.mtime) {
needs_compile = false
}
}
if (needs_compile) {
var result = build.compile_file(src, obj, pkg_options)
if (!result) {
log.error("Build failed")
$_.stop()
}
}
objects.push(obj)
}
}
}
// Collect C files from local project (only if not building from cell source directory)
if (cell_dir != '.') {
var local_config = shop.load_config() || {}
var local_ldflags = build.get_flags(local_config, platform, 'LDFLAGS')
if (local_ldflags) {
if (ldflags != '') ldflags += ' '
ldflags += local_ldflags
}
var local_files = build.list_files('.')
var local_c_files = build.select_c_files(local_files, target)
if (local_c_files.length > 0) {
log.console("Compiling " + text(local_c_files.length) + " local C files")
var local_cflags = build.get_flags(local_config, platform, 'CFLAGS')
for (var f = 0; f < local_c_files.length; f++) {
var src = local_c_files[f]
var obj = build_dir + '/local/' + local_c_files[f] + '.o'
var safe_name = local_c_files[f].substring(0, local_c_files[f].lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_')
var use_name = 'js_local_' + safe_name + '_use'
var local_options = {
target: target,
cflags: local_cflags,
includes: [cell_dir + '/source', '.'],
defines: { CELL_USE_NAME: use_name }
}
var needs_compile = true
if (fd.is_file(obj)) {
var src_stat = fd.stat(src)
var obj_stat = fd.stat(obj)
if (src_stat && obj_stat && src_stat.mtime <= obj_stat.mtime) {
needs_compile = false
}
}
if (needs_compile) {
var result = build.compile_file(src, obj, local_options)
if (!result) {
log.error("Build failed")
$_.stop()
}
}
objects.push(obj)
}
}
}
log.console("Compiled " + text(objects.length) + " object files")
// Link into executable
var exe_ext = build.get_exe_ext(target)
var exe_name = build_dir + '/cell' + exe_ext
var link_options = {
target: target,
ldflags: ldflags,
libs: []
}
// Add platform-specific libraries
if (!target || platform == 'macOS' || platform == 'darwin') {
// macOS needs no extra libs for static build
} else if (target == 'windows') {
link_options.libs.push('ws2_32')
link_options.libs.push('winmm')
}
var result = build.link_executable(objects, exe_name, link_options)
if (!result) {
log.error("Linking failed")
$_.stop()
}
// TODO: Append core.qop to executable
// For now, just report success
log.console("")
log.console("Build complete: " + exe_name)
log.console("")
log.console("Note: To create a fully functional cell binary, you need to append core.qop:")
log.console(" cat core.qop >> " + exe_name)
$_.stop()

471
scripts/build.cm Normal file
View File

@@ -0,0 +1,471 @@
// build.cm - Build utilities for compiling Cell projects and binaries
var fd = use('fd')
var toml = use('toml')
var json = use('json')
var crypto = use('crypto')
var utf8 = use('utf8')
var os_mod = use('os')
var Build = {}
// Embedded cross-compilation toolchain configurations
Build.toolchains = {
playdate: {
binaries: {
c: 'arm-none-eabi-gcc',
cpp: 'arm-none-eabi-g++',
ar: 'arm-none-eabi-ar',
strip: 'arm-none-eabi-strip',
objcopy: 'arm-none-eabi-objcopy',
ld: 'arm-none-eabi-gcc'
},
host_machine: {
system: 'playdate',
cpu_family: 'arm',
cpu: 'cortex-m7',
endian: 'little'
},
c_args: ['-mcpu=cortex-m7', '-mthumb', '-mfloat-abi=hard', '-mfpu=fpv5-sp-d16', '-fno-exceptions'],
c_link_args: ['-mcpu=cortex-m7', '-mthumb', '-mfloat-abi=hard', '-mfpu=fpv5-sp-d16', '-nostartfiles', "-T/Users/john/Developer/PlaydateSDK/C_API/buildsupport/link_map.ld"]
},
windows: {
binaries: {
c: 'x86_64-w64-mingw32-gcc',
cpp: 'x86_64-w64-mingw32-g++',
ar: 'x86_64-w64-mingw32-ar',
windres: 'x86_64-w64-mingw32-windres',
strip: 'x86_64-w64-mingw32-strip'
},
host_machine: {
system: 'windows',
cpu_family: 'x86_64',
cpu: 'x86_64',
endian: 'little'
},
c_args: [],
c_link_args: []
},
linux: {
binaries: {
c: 'zig cc -target x86_64-linux-musl',
cpp: 'zig c++ -target x86_64-linux-musl',
ar: 'zig ar',
strip: 'strip'
},
host_machine: {
system: 'linux',
cpu_family: 'x86_64',
cpu: 'x86_64',
endian: 'little'
},
c_args: [],
c_link_args: []
}
}
// Target aliases for convenience
Build.target_aliases = {
'win': 'windows',
'win64': 'windows',
'mingw': 'windows',
'pd': 'playdate'
}
// Resolve target name (handle aliases)
Build.resolve_target = function(target) {
if (!target) return null
target = target.toLowerCase()
return Build.target_aliases[target] || target
}
// Get toolchain for a target (null = host)
Build.get_toolchain = function(target) {
if (!target) return null
target = Build.resolve_target(target)
return Build.toolchains[target] || null
}
// Get compiler command for target
Build.get_cc = function(target) {
var tc = Build.get_toolchain(target)
if (tc && tc.binaries && tc.binaries.c) return tc.binaries.c
return 'cc'
}
// Get archiver command for target
Build.get_ar = function(target) {
var tc = Build.get_toolchain(target)
if (tc && tc.binaries && tc.binaries.ar) return tc.binaries.ar
return 'ar'
}
// Get extra C flags for target
Build.get_target_cflags = function(target) {
var tc = Build.get_toolchain(target)
if (tc && tc.c_args) return tc.c_args.join(' ')
return ''
}
// Get extra link flags for target
Build.get_target_ldflags = function(target) {
var tc = Build.get_toolchain(target)
if (tc && tc.c_link_args) return tc.c_link_args.join(' ')
return ''
}
// Get target system name
Build.get_target_system = function(target) {
var tc = Build.get_toolchain(target)
if (tc && tc.host_machine && tc.host_machine.system) return tc.host_machine.system
return null
}
// Get executable extension for target
Build.get_exe_ext = function(target) {
var sys = Build.get_target_system(target)
if (sys == 'windows') return '.exe'
return ''
}
// Get shared library extension for target
Build.get_dylib_ext = function(target) {
var sys = Build.get_target_system(target)
if (sys == 'windows') return '.dll'
if (sys == 'darwin' || sys == 'macOS') return '.dylib'
return '.so'
}
// Ensure directory exists
function ensure_dir(path) {
if (fd.stat(path).isDirectory) return true
var parts = path.split('/')
var current = ''
for (var i = 0; i < parts.length; i++) {
if (parts[i] == '') continue
current += parts[i] + '/'
if (!fd.stat(current).isDirectory) {
fd.mkdir(current)
}
}
return true
}
Build.ensure_dir = ensure_dir
// Get hash of a string
function get_hash(str) {
return text(crypto.blake2(utf8.encode(str)), 'h')
}
Build.get_hash = get_hash
// List all files in a directory recursively
function list_files_recursive(dir, prefix, results) {
prefix = prefix || ""
results = results || []
var list = fd.readdir(dir)
if (!list) return results
for (var i = 0; i < list.length; i++) {
var item = list[i]
if (item == '.' || item == '..') continue
if (item.startsWith('.')) continue
var full_path = dir + "/" + item
var rel_path = prefix ? prefix + "/" + item : item
var st = fd.stat(full_path)
if (st.isDirectory) {
// Skip build directories
if (item == 'build' || item.startsWith('build_')) continue
list_files_recursive(full_path, rel_path, results)
} else {
results.push(rel_path)
}
}
return results
}
Build.list_files = list_files_recursive
// Select C files for a target, handling target-specific variants
// e.g., if target is 'playdate', prefer fd_playdate.c over fd.c
// Platform-specific files can be in any directory (source/ or scripts/)
Build.select_c_files = function(files, target) {
var c_files = []
var target_suffix = target ? '_' + target : null
// Known target suffixes for platform-specific files
var known_targets = ['playdate', 'windows', 'linux', 'macos', 'threaded', 'single']
// First pass: collect all files and identify platform-specific ones
// Group by generic name (ignoring directory) to find cross-directory variants
var name_groups = {} // generic_name+ext -> { generics: [], variants: { target: file } }
for (var i = 0; i < files.length; i++) {
var file = files[i]
if (!file.endsWith('.c') && !file.endsWith('.cpp')) continue
var ext = file.endsWith('.cpp') ? '.cpp' : '.c'
var base = file.substring(0, file.length - ext.length)
var dir = ''
var name = base
var slash = base.lastIndexOf('/')
if (slash >= 0) {
dir = base.substring(0, slash + 1)
name = base.substring(slash + 1)
}
// Check if this is a target-specific file
var is_target_specific = false
var target_name = null
var generic_name = name
for (var t = 0; t < known_targets.length; t++) {
var suffix = '_' + known_targets[t]
if (name.endsWith(suffix)) {
is_target_specific = true
target_name = known_targets[t]
generic_name = name.substring(0, name.length - suffix.length)
break
}
}
// Group key is just the generic name + extension (ignoring directory)
// This allows source/fd_playdate.c to override scripts/fd.c
var group_key = generic_name + ext
if (!name_groups[group_key]) {
name_groups[group_key] = { generics: [], variants: {} }
}
if (is_target_specific) {
// Platform-specific file - store by target name
name_groups[group_key].variants[target_name] = file
} else {
// Generic file - could have multiple in different directories
name_groups[group_key].generics.push(file)
}
}
// Second pass: select appropriate file from each group
for (var key in name_groups) {
var group = name_groups[key]
var selected = null
// If we have a target, check for target-specific variant first
if (target && group.variants[target]) {
selected = group.variants[target]
} else if (group.generics.length > 0) {
// Use generic if no target-specific variant
// If multiple generics exist (shouldn't happen normally), use first one
selected = group.generics[0]
} else {
// No generic, only variants exist
// This handles cases like scheduler_threaded.c vs scheduler_playdate.c
// where there's no generic scheduler.c
if (target) {
// Only include if it's for our target
if (group.variants[target]) {
selected = group.variants[target]
}
} else {
// No target specified, prefer 'threaded' variant as default
if (group.variants['threaded']) {
selected = group.variants['threaded']
}
}
}
if (selected) {
c_files.push(selected)
}
}
return c_files
}
// Get build directory for a target
Build.get_build_dir = function(target) {
if (!target) return '.cell/build/static'
return '.cell/build/' + target
}
// Compile a single C file
// Returns object path on success, null on failure
Build.compile_file = function(src_path, obj_path, options) {
options = options || {}
var target = options.target
var cflags = options.cflags || ''
var includes = options.includes || []
var defines = options.defines || {}
var module_dir = options.module_dir || '.'
var cc = Build.get_cc(target)
var target_cflags = Build.get_target_cflags(target)
ensure_dir(obj_path.substring(0, obj_path.lastIndexOf('/')))
var full_cmd
if (module_dir != '.') {
// If compiling in module dir, we need to adjust paths
// Adjust includes - prefix with $HERE if relative
var include_str = ''
for (var i = 0; i < includes.length; i++) {
var inc = includes[i]
if (!inc.startsWith('/')) {
inc = '"$HERE/' + inc + '"'
}
include_str += ' -I' + inc
}
var define_str = ''
for (var k in defines) {
if (defines[k] == true) {
define_str += ' -D' + k
} else {
define_str += ' -D' + k + '=' + defines[k]
}
}
var compile_flags = ' -O3' + include_str + define_str
if (target_cflags) compile_flags += ' ' + target_cflags
if (cflags) compile_flags += ' ' + cflags
// Adjust source path relative to module dir
var src_file = src_path
if (src_path.startsWith(module_dir + '/')) {
src_file = src_path.substring(module_dir.length + 1)
} else if (!src_path.startsWith('/')) {
src_file = '"$HERE/' + src_path + '"'
}
// Adjust output path to be absolute/relative to HERE
var out_file = obj_path
if (!out_file.startsWith('/')) {
out_file = '"$HERE/' + out_file + '"'
}
var cc_cmd = cc + ' -c' + compile_flags + ' ' + src_file + ' -o ' + out_file
full_cmd = 'HERE=$(pwd); cd ' + module_dir + ' && ' + cc_cmd
} else {
// Standard compilation from current dir
var include_str = ''
for (var i = 0; i < includes.length; i++) {
include_str += ' -I' + includes[i]
}
var define_str = ''
for (var k in defines) {
if (defines[k] == true) {
define_str += ' -D' + k
} else {
define_str += ' -D' + k + '=' + defines[k]
}
}
var base_cmd = cc + ' -c'
var compile_flags = ' -O3' + include_str + define_str
if (target_cflags) compile_flags += ' ' + target_cflags
if (cflags) compile_flags += ' ' + cflags
full_cmd = base_cmd + compile_flags + ' ' + src_path + ' -o ' + obj_path
}
log.console("Compiling " + src_path)
var ret = os_mod.system(full_cmd)
if (ret != 0) {
log.error("Compilation failed: " + src_path)
return null
}
return obj_path
}
// Link object files into a static library
Build.link_static = function(objects, output, options) {
options = options || {}
var target = options.target
var ar = Build.get_ar(target)
ensure_dir(output.substring(0, output.lastIndexOf('/')))
var objs_str = objects.join(' ')
var cmd = ar + ' rcs ' + output + ' ' + objs_str
log.console("Creating static library " + output)
var ret = os_mod.system(cmd)
if (ret != 0) {
log.error("Archiving failed")
return null
}
return output
}
// Link object files into an executable
Build.link_executable = function(objects, output, options) {
options = options || {}
var target = options.target
var ldflags = options.ldflags || ''
var libs = options.libs || []
var cc = Build.get_cc(target)
var target_ldflags = Build.get_target_ldflags(target)
var exe_ext = Build.get_exe_ext(target)
if (!output.endsWith(exe_ext)) {
output = output + exe_ext
}
ensure_dir(output.substring(0, output.lastIndexOf('/')))
var objs_str = objects.join(' ')
var libs_str = ''
for (var i = 0; i < libs.length; i++) {
libs_str += ' -l' + libs[i]
}
var link_flags = ''
if (target_ldflags) link_flags += ' ' + target_ldflags
if (ldflags) link_flags += ' ' + ldflags
var cmd = cc + ' ' + objs_str + libs_str + link_flags + ' -o ' + output
log.console("Linking " + output)
var ret = os_mod.system(cmd)
if (ret != 0) {
log.error("Linking failed")
log.error(cmd)
return null
}
return output
}
// Get flags from config for a platform
Build.get_flags = function(config, platform, key) {
var flags = ''
if (config.compilation && config.compilation[key]) {
flags += config.compilation[key]
}
if (config.compilation && config.compilation[platform] && config.compilation[platform][key]) {
if (flags != '') flags += ' '
flags += config.compilation[platform][key]
}
return flags
}
// Load config from a directory
Build.load_config = function(dir) {
var path = dir + '/.cell/cell.toml'
if (!fd.is_file(path)) return {}
var content = fd.slurp(path)
if (!content || !content.length) return {}
return toml.decode(text(content))
}
return Build

9
scripts/enet_playdate.c Normal file
View File

@@ -0,0 +1,9 @@
// enet_playdate.c - ENet stub for Playdate
// ENet networking is not supported on Playdate, so this module returns an empty object.
#include "cell.h"
JSValue js_enet_use(JSContext *js) {
// Return empty object - ENet is not available on Playdate
return JS_NewObject(js);
}

View File

@@ -4,66 +4,10 @@
#include <stdio.h>
#include <time.h>
// --- Playdate File API Definitions ---
#include "pd_api.h"
#ifndef pdext_file_h
#define pdext_file_h
#if TARGET_EXTENSION
typedef void SDFile;
typedef enum
{
kFileRead = (1<<0),
kFileReadData = (1<<1),
kFileWrite = (1<<2),
kFileAppend = (2<<2)
} FileOptions;
typedef struct
{
int isdir;
unsigned int size;
int m_year;
int m_month;
int m_day;
int m_hour;
int m_minute;
int m_second;
} FileStat;
#endif
#if !defined(SEEK_SET)
# define SEEK_SET 0 /* Seek from beginning of file. */
# define SEEK_CUR 1 /* Seek from current position. */
# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */
#endif
struct playdate_file
{
const char* (*geterr)(void);
int (*listfiles)(const char* path, void (*callback)(const char* path, void* userdata), void* userdata, int showhidden);
int (*stat)(const char* path, FileStat* stat);
int (*mkdir)(const char* path);
int (*unlink)(const char* name, int recursive);
int (*rename)(const char* from, const char* to);
SDFile* (*open)(const char* name, FileOptions mode);
int (*close)(SDFile* file);
int (*read)(SDFile* file, void* buf, unsigned int len);
int (*write)(SDFile* file, const void* buf, unsigned int len);
int (*flush)(SDFile* file);
int (*tell)(SDFile* file);
int (*seek)(SDFile* file, int pos, int whence);
};
#endif /* pdext_file_h */
// Assumed global pointer to the file API
extern struct playdate_file* pd_file;
// Global Playdate API pointers - defined in main_playdate.c
extern const struct playdate_file *pd_file;
// --- Helper Functions ---

248
scripts/http_playdate.c Normal file
View File

@@ -0,0 +1,248 @@
// http_playdate.c - HTTP module for Playdate using Playdate Network API
// Note: Playdate HTTP does not support SSL/HTTPS
#include "cell.h"
#include "pd_api.h"
#include <string.h>
#include <stdlib.h>
// Global Playdate API pointers - defined in main_playdate.c
extern const struct playdate_network *pd_network;
extern const struct playdate_sys *pd_sys;
#if TARGET_EXTENSION
// Context for async HTTP fetch
typedef struct {
JSContext *js;
HTTPConnection *conn;
uint8_t *data;
size_t data_len;
size_t data_cap;
int complete;
int success;
int status_code;
} http_fetch_ctx;
static void http_response_callback(HTTPConnection *conn) {
http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
if (!ctx) return;
ctx->status_code = pd_network->http->getResponseStatus(conn);
// Read all available data
while (1) {
size_t avail = pd_network->http->getBytesAvailable(conn);
if (avail == 0) break;
// Grow buffer if needed
if (ctx->data_len + avail > ctx->data_cap) {
size_t new_cap = ctx->data_cap * 2;
if (new_cap < ctx->data_len + avail) new_cap = ctx->data_len + avail + 4096;
uint8_t *new_data = realloc(ctx->data, new_cap);
if (!new_data) {
ctx->success = 0;
ctx->complete = 1;
return;
}
ctx->data = new_data;
ctx->data_cap = new_cap;
}
int read = pd_network->http->read(conn, ctx->data + ctx->data_len, (unsigned int)avail);
if (read < 0) {
ctx->success = 0;
ctx->complete = 1;
return;
}
ctx->data_len += read;
}
}
static void http_complete_callback(HTTPConnection *conn) {
http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
if (!ctx) return;
// Read any remaining data
http_response_callback(conn);
ctx->success = (ctx->status_code >= 200 && ctx->status_code < 400);
ctx->complete = 1;
}
static void http_closed_callback(HTTPConnection *conn) {
http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn);
if (!ctx) return;
ctx->complete = 1;
}
// Parse URL into host, port, path, and check if HTTPS
static int parse_url(const char *url, char **host, int *port, char **path, int *is_https) {
*host = NULL;
*path = NULL;
*port = 80;
*is_https = 0;
const char *p = url;
// Check scheme
if (strncmp(p, "https://", 8) == 0) {
*is_https = 1;
*port = 443;
p += 8;
} else if (strncmp(p, "http://", 7) == 0) {
p += 7;
} else {
return -1; // Invalid scheme
}
// Find end of host (either :, /, or end of string)
const char *host_start = p;
const char *host_end = p;
while (*host_end && *host_end != ':' && *host_end != '/') host_end++;
size_t host_len = host_end - host_start;
*host = malloc(host_len + 1);
if (!*host) return -1;
memcpy(*host, host_start, host_len);
(*host)[host_len] = '\0';
p = host_end;
// Check for port
if (*p == ':') {
p++;
*port = atoi(p);
while (*p && *p != '/') p++;
}
// Get path (default to "/" if none)
if (*p == '/') {
*path = strdup(p);
} else {
*path = strdup("/");
}
if (!*path) {
free(*host);
*host = NULL;
return -1;
}
return 0;
}
// Performs a blocking HTTP GET and returns a QuickJS Blob of the body
static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 1 || !JS_IsString(argv[0]))
return JS_ThrowTypeError(ctx, "fetch: URL string required");
if (!pd_network || !pd_network->http) {
return JS_ThrowInternalError(ctx, "fetch: Playdate network API not available");
}
const char *url = JS_ToCString(ctx, argv[0]);
if (!url) return JS_ThrowTypeError(ctx, "fetch: invalid URL");
char *host = NULL;
char *path = NULL;
int port = 80;
int is_https = 0;
if (parse_url(url, &host, &port, &path, &is_https) < 0) {
JS_FreeCString(ctx, url);
return JS_ThrowTypeError(ctx, "fetch: failed to parse URL");
}
JS_FreeCString(ctx, url);
// Playdate doesn't support HTTPS
if (is_https) {
free(host);
free(path);
return JS_ThrowTypeError(ctx, "fetch: HTTPS not supported on Playdate");
}
// Create HTTP connection
HTTPConnection *conn = pd_network->http->newConnection(host, port, 0);
free(host);
if (!conn) {
free(path);
return JS_ThrowInternalError(ctx, "fetch: failed to create connection");
}
// Set up context
http_fetch_ctx fetch_ctx = {0};
fetch_ctx.js = ctx;
fetch_ctx.conn = conn;
fetch_ctx.data = malloc(4096);
fetch_ctx.data_cap = 4096;
fetch_ctx.data_len = 0;
fetch_ctx.complete = 0;
fetch_ctx.success = 0;
if (!fetch_ctx.data) {
pd_network->http->release(conn);
free(path);
return JS_ThrowInternalError(ctx, "fetch: malloc failed");
}
pd_network->http->setUserdata(conn, &fetch_ctx);
pd_network->http->setResponseCallback(conn, http_response_callback);
pd_network->http->setRequestCompleteCallback(conn, http_complete_callback);
pd_network->http->setConnectionClosedCallback(conn, http_closed_callback);
pd_network->http->setConnectTimeout(conn, 30000); // 30 second timeout
pd_network->http->setReadTimeout(conn, 30000);
// Start the GET request
PDNetErr err = pd_network->http->get(conn, path, NULL, 0);
free(path);
if (err != NET_OK) {
free(fetch_ctx.data);
pd_network->http->release(conn);
return JS_ThrowInternalError(ctx, "fetch: request failed with error %d", err);
}
// Poll until complete (blocking)
// Note: This is a simple blocking implementation. In a real game,
// you'd want to use async callbacks instead.
while (!fetch_ctx.complete) {
// Small delay to avoid busy-waiting
pd_sys->delay(10);
}
pd_network->http->close(conn);
pd_network->http->release(conn);
if (!fetch_ctx.success) {
free(fetch_ctx.data);
return JS_ThrowTypeError(ctx, "fetch: request failed (status %d)", fetch_ctx.status_code);
}
// Return a Blob wrapping the data
JSValue blob = js_new_blob_stoned_copy(ctx, fetch_ctx.data, fetch_ctx.data_len);
free(fetch_ctx.data);
return blob;
}
#else
// Simulator/non-extension build - provide stub
static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
return JS_ThrowInternalError(ctx, "fetch: not available in simulator");
}
#endif
static const JSCFunctionListEntry js_http_funcs[] = {
JS_CFUNC_DEF("fetch", 2, js_fetch_playdate),
};
JSValue js_http_use(JSContext *js) {
JSValue obj = JS_NewObject(js);
JS_SetPropertyFunctionList(js, obj, js_http_funcs,
sizeof(js_http_funcs)/sizeof(js_http_funcs[0]));
return obj;
}

View File

@@ -537,6 +537,17 @@ JSC_CCALL(os_random,
return JS_NewFloat64(js, cell_random_fit());
)
JSC_CCALL(os_getenv,
const char *name = JS_ToCString(js, argv[0]);
if (!name) return JS_EXCEPTION;
const char *value = getenv(name);
JS_FreeCString(js, name);
if (value)
ret = JS_NewString(js, value);
else
ret = JS_NULL;
)
static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, platform, 0),
MIST_FUNC_DEF(os, arch, 0),
@@ -557,6 +568,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, internal_exists, 1),
MIST_FUNC_DEF(os, print, 1),
MIST_FUNC_DEF(os, random, 0),
MIST_FUNC_DEF(os, getenv, 1),
};
JSValue js_os_use(JSContext *js) {

178
scripts/os_playdate.c Normal file
View File

@@ -0,0 +1,178 @@
// os_playdate.c - OS module for Playdate
// Provides system information and utilities for Playdate platform
#include "cell.h"
#include "pd_api.h"
#include <stdlib.h>
#include <string.h>
// Global Playdate API pointers - defined in main_playdate.c
extern const struct playdate_sys *pd_sys;
// cell_ns - nanoseconds since some epoch (used by scheduler)
uint64_t cell_ns(void)
{
// Playdate provides milliseconds, convert to nanoseconds
if (pd_sys) {
unsigned int ms = pd_sys->getCurrentTimeMilliseconds();
return (uint64_t)ms * 1000000ULL;
}
return 0;
}
// cell_sleep - sleep for specified seconds
void cell_sleep(double seconds)
{
if (pd_sys) {
pd_sys->delay((uint32_t)(seconds * 1000));
}
}
JSC_CCALL(os_now,
return number2js(js, cell_ns());
)
JSC_CCALL(os_sleep,
double secs = js2number(js, argv[0]);
cell_sleep(secs);
)
static JSValue js_os_totalmem(JSContext *js, JSValue self, int argc, JSValue *argv) {
// Playdate has 16MB RAM, but we don't have direct access to memory stats
return JS_NewInt64(js, 16); // 16 MB
}
static JSValue js_os_platform(JSContext *js, JSValue self, int argc, JSValue *argv) {
return JS_NewString(js, "Playdate");
}
static JSValue js_os_hostname(JSContext *js, JSValue self, int argc, JSValue *argv) {
return JS_NewString(js, "playdate");
}
static JSValue js_os_arch(JSContext *js, JSValue self, int argc, JSValue *argv) {
return JS_NewString(js, "arm");
}
static JSValue js_os_freemem(JSContext *js, JSValue self, int argc, JSValue *argv) {
// No way to get free memory on Playdate
return JS_NewInt64(js, 0);
}
static JSValue js_os_version(JSContext *js, JSValue self, int argc, JSValue *argv) {
#if TARGET_EXTENSION
if (pd_sys) {
const struct PDInfo *info = pd_sys->getSystemInfo();
if (info) {
char buf[32];
snprintf(buf, sizeof(buf), "%u", info->osversion);
return JS_NewString(js, buf);
}
}
#endif
return JS_NewString(js, "unknown");
}
JSC_CCALL(os_mallinfo,
ret = JS_NewObject(js);
)
static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) {
// No rusage on Playdate
return JS_NewObject(js);
}
JSC_SCALL(os_system,
// system() not available on Playdate
ret = JS_NewInt32(js, -1);
)
JSC_CCALL(os_exit,
// Can't really exit on Playdate, but we can try
exit(0);
)
// dylib functions - not supported on Playdate
static JSValue js_os_dylib_open(JSContext *js, JSValue self, int argc, JSValue *argv) {
return JS_ThrowInternalError(js, "dylib_open: not supported on Playdate");
}
static JSValue js_os_dylib_symbol(JSContext *js, JSValue self, int argc, JSValue *argv) {
return JS_ThrowInternalError(js, "dylib_symbol: not supported on Playdate");
}
static JSValue js_os_dylib_has_symbol(JSContext *js, JSValue self, int argc, JSValue *argv) {
return JS_NewBool(js, 0);
}
JSC_CCALL(os_print,
size_t len;
const char *str = JS_ToCStringLen(js, &len, argv[0]);
if (pd_sys) {
pd_sys->logToConsole("%.*s", (int)len, str);
}
JS_FreeCString(js, str);
)
static JSValue js_os_load_internal(JSContext *js, JSValue self, int argc, JSValue *argv) {
// No dlsym on Playdate - internal modules are linked statically
return JS_NULL;
}
static JSValue js_os_internal_exists(JSContext *js, JSValue self, int argc, JSValue *argv) {
return JS_NewBool(js, 0);
}
// Random number generation
int randombytes(void *buf, size_t n) {
// Playdate doesn't have a crypto RNG, use a simple PRNG seeded from time
// This is NOT cryptographically secure!
uint8_t *p = (uint8_t *)buf;
static uint32_t seed = 0;
if (seed == 0 && pd_sys) {
seed = pd_sys->getCurrentTimeMilliseconds();
}
for (size_t i = 0; i < n; i++) {
seed = seed * 1103515245 + 12345;
p[i] = (uint8_t)(seed >> 16);
}
return 0;
}
JSC_CCALL(os_random,
return JS_NewFloat64(js, cell_random_fit());
)
JSC_CCALL(os_getenv,
// No environment variables on Playdate
return JS_NULL;
)
static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, platform, 0),
MIST_FUNC_DEF(os, arch, 0),
MIST_FUNC_DEF(os, totalmem, 0),
MIST_FUNC_DEF(os, freemem, 0),
MIST_FUNC_DEF(os, hostname, 0),
MIST_FUNC_DEF(os, version, 0),
MIST_FUNC_DEF(os, now, 0),
MIST_FUNC_DEF(os, rusage, 0),
MIST_FUNC_DEF(os, mallinfo, 0),
MIST_FUNC_DEF(os, system, 1),
MIST_FUNC_DEF(os, exit, 0),
MIST_FUNC_DEF(os, sleep, 1),
MIST_FUNC_DEF(os, dylib_open, 1),
MIST_FUNC_DEF(os, dylib_symbol, 2),
MIST_FUNC_DEF(os, dylib_has_symbol, 2),
MIST_FUNC_DEF(os, load_internal, 1),
MIST_FUNC_DEF(os, internal_exists, 1),
MIST_FUNC_DEF(os, print, 1),
MIST_FUNC_DEF(os, random, 0),
MIST_FUNC_DEF(os, getenv, 1),
};
JSValue js_os_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_os_funcs, countof(js_os_funcs));
return mod;
}

View File

@@ -8,6 +8,7 @@ var js = use('js')
var crypto = use('crypto')
var utf8 = use('utf8')
var blob = use('blob')
var build_utils = use('build')
var qop
var core_qop
@@ -203,20 +204,8 @@ Shop.get_c_symbol = function get_c_symbol(name) {
return os.load_internal(symname)
}
function ensure_dir(path) {
if (fd.stat(path).isDirectory) return true
var parts = path.split('/')
var current = ''
for (var i = 0; i < parts.length; i++) {
if (parts[i] == '') continue
current += parts[i] + '/'
if (!fd.stat(current).isDirectory) {
fd.mkdir(current)
}
}
return true
}
// Use ensure_dir from build_utils
var ensure_dir = build_utils.ensure_dir
Shop.load_config = function(module) {
var content
@@ -680,6 +669,19 @@ function resolve_c_symbol(path, package_context)
local = null // handled via candidates below
}
// First check for statically linked/internal symbols
var local_candidates = package_context ? symbol_candidates(local_path, local_sym_base) : [local]
for (var li = 0; li < local_candidates.length; li++) {
var lc = local_candidates[li]
if (os.internal_exists(lc))
return {
symbol: function() { return os.load_internal(lc); },
scope: SCOPE_LOCAL,
path: lc
};
}
// Then try dynamic library
var build_dir = get_build_dir(package_context)
var local_dl_name = build_dir + '/cellmod' + dylib_ext
@@ -701,29 +703,30 @@ function resolve_c_symbol(path, package_context)
}
}
// Try static linking fallback
var local_candidates = package_context ? symbol_candidates(local_path, local_sym_base) : [local]
for (var li = 0; li < local_candidates.length; li++) {
var lc = local_candidates[li]
if (os.internal_exists(lc))
return {
symbol: function() { return os.load_internal(lc); },
scope: SCOPE_LOCAL,
path: lc
};
}
// If 'path' has a package alias (e.g. 'prosperon/sprite'), try to resolve it
var pkg_alias = get_import_package(path)
if (pkg_alias) {
var canon_pkg = get_normalized_package(path, package_context)
if (canon_pkg) {
var build_dir = get_build_dir(canon_pkg)
var dl_path = build_dir + '/cellmod' + dylib_ext
var pkg_build_dir = get_build_dir(canon_pkg)
var dl_path = pkg_build_dir + '/cellmod' + dylib_ext
var mod_name = get_import_name(path)
var mod_sym = mod_name.replace(/\//g, '_').replace(/-/g, '_').replace(/\./g, '_')
var sym_names = symbol_candidates(canon_pkg, mod_sym)
// First check internal/static symbols for package
for (var sii = 0; sii < sym_names.length; sii++) {
var sym_name = sym_names[sii]
if (os.internal_exists(sym_name))
return {
symbol: function() { return os.load_internal(sym_name) },
scope: SCOPE_PACKAGE,
package: canon_pkg,
path: sym_name
};
}
// Then try dynamic library for package
if (fd.is_file(dl_path)) {
if (!open_dls[dl_path]) open_dls[dl_path] = os.dylib_open(dl_path)
if (open_dls[dl_path]) {
@@ -739,19 +742,9 @@ function resolve_c_symbol(path, package_context)
}
}
}
for (var sii = 0; sii < sym_names.length; sii++) {
var sym_name = sym_names[sii]
if (os.internal_exists(sym_name))
return {
symbol: function() { return os.load_internal(sym_name) },
scope: SCOPE_PACKAGE,
package: canon_pkg,
path: sym_name
};
}
}
}
var core_sym = `js_${path.replace(/\//g, '_')}_use`;
if (os.internal_exists(core_sym))
return {
@@ -1312,9 +1305,8 @@ Shop.build_package = function(package)
var c_objects = []
function get_hash(str) {
return text(crypto.blake2(utf8.encode(str)), 'h')
}
// Use get_hash from build_utils
var get_hash = build_utils.get_hash
for (var i=0; i<files.length; i++) {
var file = files[i]

115
scripts/socket_playdate.c Normal file
View File

@@ -0,0 +1,115 @@
// socket_playdate.c - Socket stub for Playdate
// Raw sockets are not directly supported on Playdate.
// Use the http module for HTTP requests or enet for game networking.
#include "cell.h"
// All socket functions throw "not supported" errors
JSC_CCALL(socket_getaddrinfo,
return JS_ThrowInternalError(js, "socket.getaddrinfo: not supported on Playdate");
)
JSC_CCALL(socket_socket,
return JS_ThrowInternalError(js, "socket.socket: not supported on Playdate");
)
JSC_CCALL(socket_bind,
return JS_ThrowInternalError(js, "socket.bind: not supported on Playdate");
)
JSC_CCALL(socket_connect,
return JS_ThrowInternalError(js, "socket.connect: not supported on Playdate");
)
JSC_CCALL(socket_listen,
return JS_ThrowInternalError(js, "socket.listen: not supported on Playdate");
)
JSC_CCALL(socket_accept,
return JS_ThrowInternalError(js, "socket.accept: not supported on Playdate");
)
JSC_CCALL(socket_send,
return JS_ThrowInternalError(js, "socket.send: not supported on Playdate");
)
JSC_CCALL(socket_recv,
return JS_ThrowInternalError(js, "socket.recv: not supported on Playdate");
)
JSC_CCALL(socket_sendto,
return JS_ThrowInternalError(js, "socket.sendto: not supported on Playdate");
)
JSC_CCALL(socket_recvfrom,
return JS_ThrowInternalError(js, "socket.recvfrom: not supported on Playdate");
)
JSC_CCALL(socket_shutdown,
return JS_ThrowInternalError(js, "socket.shutdown: not supported on Playdate");
)
JSC_CCALL(socket_getpeername,
return JS_ThrowInternalError(js, "socket.getpeername: not supported on Playdate");
)
JSC_CCALL(socket_gethostname,
return JS_NewString(js, "playdate");
)
JSC_CCALL(socket_gai_strerror,
return JS_NewString(js, "not supported on Playdate");
)
JSC_CCALL(socket_setsockopt,
return JS_ThrowInternalError(js, "socket.setsockopt: not supported on Playdate");
)
JSC_CCALL(socket_close,
return JS_ThrowInternalError(js, "socket.close: not supported on Playdate");
)
static const JSCFunctionListEntry js_socket_funcs[] = {
MIST_FUNC_DEF(socket, getaddrinfo, 3),
MIST_FUNC_DEF(socket, socket, 3),
MIST_FUNC_DEF(socket, bind, 2),
MIST_FUNC_DEF(socket, connect, 2),
MIST_FUNC_DEF(socket, listen, 2),
MIST_FUNC_DEF(socket, accept, 1),
MIST_FUNC_DEF(socket, send, 3),
MIST_FUNC_DEF(socket, recv, 3),
MIST_FUNC_DEF(socket, sendto, 4),
MIST_FUNC_DEF(socket, recvfrom, 3),
MIST_FUNC_DEF(socket, shutdown, 2),
MIST_FUNC_DEF(socket, getpeername, 1),
MIST_FUNC_DEF(socket, gethostname, 0),
MIST_FUNC_DEF(socket, gai_strerror, 1),
MIST_FUNC_DEF(socket, setsockopt, 4),
MIST_FUNC_DEF(socket, close, 1),
};
JSValue js_socket_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_socket_funcs, countof(js_socket_funcs));
// Add constants (even though they won't be useful)
JS_SetPropertyStr(js, mod, "AF_UNSPEC", JS_NewInt32(js, 0));
JS_SetPropertyStr(js, mod, "AF_INET", JS_NewInt32(js, 2));
JS_SetPropertyStr(js, mod, "AF_INET6", JS_NewInt32(js, 10));
JS_SetPropertyStr(js, mod, "AF_UNIX", JS_NewInt32(js, 1));
JS_SetPropertyStr(js, mod, "SOCK_STREAM", JS_NewInt32(js, 1));
JS_SetPropertyStr(js, mod, "SOCK_DGRAM", JS_NewInt32(js, 2));
JS_SetPropertyStr(js, mod, "AI_PASSIVE", JS_NewInt32(js, 1));
JS_SetPropertyStr(js, mod, "SHUT_RD", JS_NewInt32(js, 0));
JS_SetPropertyStr(js, mod, "SHUT_WR", JS_NewInt32(js, 1));
JS_SetPropertyStr(js, mod, "SHUT_RDWR", JS_NewInt32(js, 2));
JS_SetPropertyStr(js, mod, "SOL_SOCKET", JS_NewInt32(js, 1));
JS_SetPropertyStr(js, mod, "SO_REUSEADDR", JS_NewInt32(js, 2));
return mod;
}

75
scripts/time_playdate.c Normal file
View File

@@ -0,0 +1,75 @@
// time_playdate.c - Time module for Playdate
#include "cell.h"
#include "pd_api.h"
// Global Playdate API pointers - defined in main_playdate.c
extern const struct playdate_sys *pd_sys;
/* ---------------------------------------------------------------- *\
Helpers
\* ---------------------------------------------------------------- */
static inline double playdate_now(void)
{
if (pd_sys) {
unsigned int ms = 0;
unsigned int secs = pd_sys->getSecondsSinceEpoch(&ms);
return (double)secs + ms / 1000.0;
}
return 0.0;
}
/* ---------------------------------------------------------------- *\
JS bindings
\* ---------------------------------------------------------------- */
static JSValue
js_time_now(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
return JS_NewFloat64(ctx, playdate_now());
}
static JSValue
js_time_computer_dst(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
// Playdate doesn't provide DST info directly
return JS_NewBool(ctx, 0);
}
static JSValue
js_time_computer_zone(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
#if TARGET_EXTENSION
if (pd_sys) {
int32_t offset = pd_sys->getTimezoneOffset();
// Playdate returns offset in seconds, convert to hours
return JS_NewFloat64(ctx, (double)offset / 3600.0);
}
#endif
return JS_NewFloat64(ctx, 0.0);
}
/* ---------------------------------------------------------------- *\
registration
\* ---------------------------------------------------------------- */
static const JSCFunctionListEntry js_time_funcs[] = {
JS_CFUNC_DEF("now", 0, js_time_now),
JS_CFUNC_DEF("computer_dst", 0, js_time_computer_dst),
JS_CFUNC_DEF("computer_zone", 0, js_time_computer_zone),
};
JSValue
js_time_use(JSContext *ctx)
{
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, obj,
js_time_funcs,
sizeof(js_time_funcs) /
sizeof(js_time_funcs[0]));
return obj;
}

View File

@@ -201,6 +201,7 @@ void script_startup(cell_rt *prt)
static void signal_handler(int sig)
{
const char *str = NULL;
#ifndef TARGET_PLAYDATE
switch (sig) {
case SIGABRT: str = "SIGABRT"; break;
case SIGFPE: str = "SIGFPE"; break;
@@ -209,6 +210,7 @@ static void signal_handler(int sig)
case SIGSEGV: str = "SIGSEGV"; break;
case SIGTERM: str = "SIGTERM"; break;
}
#endif
if (!str) return;
exit_handler();
@@ -243,12 +245,12 @@ int cell_init(int argc, char **argv)
actor_initialize();
root_cell = create_actor(startwota.data);
#ifndef TARGET_PLAYDATE
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGSEGV, signal_handler);
signal(SIGABRT, signal_handler);
#endif
actor_loop();
return 0;

49
source/main_playdate.c Normal file
View File

@@ -0,0 +1,49 @@
// main_playdate.c - Playdate entry point for Cell runtime
// This file provides the eventHandler entry point required by Playdate SDK
// and initializes the global Playdate API pointers used by other modules.
#include "cell.h"
#include "pd_api.h"
// Global Playdate API pointers - used by fd_playdate.c, http_playdate.c, etc.
PlaydateAPI *pd = NULL;
const struct playdate_file *pd_file = NULL;
const struct playdate_sys *pd_sys = NULL;
const struct playdate_network *pd_network = NULL;
// Forward declaration
extern int cell_init(int argc, char **argv);
// Playdate update callback
static int update(void *userdata)
{
// The Cell runtime uses its own event loop, so we just return 1 to continue
return 1;
}
// Playdate event handler - main entry point
#ifdef _WINDLL
__declspec(dllexport)
#endif
int eventHandler(PlaydateAPI *playdate, PDSystemEvent event, uint32_t arg)
{
(void)arg;
if (event == kEventInit) {
// Store global API pointers
pd = playdate;
pd_file = playdate->file;
pd_sys = playdate->system;
pd_network = playdate->network;
// Set up the update callback
pd_sys->setUpdateCallback(update, NULL);
// Initialize Cell runtime with no arguments
// On Playdate, we'll look for main.ce in the data folder
char *argv[] = { "cell", "main.ce", NULL };
cell_init(2, argv);
}
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +0,0 @@
/*
* QuickJS C library
*
* Copyright (c) 2017-2018 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef QUICKJS_LIBC_H
#define QUICKJS_LIBC_H
#include <stdio.h>
#include <stdlib.h>
#include "quickjs.h"
#ifdef __cplusplus
extern "C" {
#endif
JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name);
JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name);
void js_std_add_helpers(JSContext *ctx, int argc, char **argv);
void js_std_loop(JSContext *ctx);
JSValue js_std_await(JSContext *ctx, JSValue obj);
void js_std_init_handlers(JSRuntime *rt);
void js_std_free_handlers(JSRuntime *rt);
void js_std_dump_error(JSContext *ctx);
uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename);
int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val,
JS_BOOL use_realpath, JS_BOOL is_main);
int js_module_test_json(JSContext *ctx, JSValueConst attributes);
int js_module_check_attributes(JSContext *ctx, void *opaque, JSValueConst attributes);
JSModuleDef *js_module_loader(JSContext *ctx,
const char *module_name, void *opaque,
JSValueConst attributes);
void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len,
int flags);
void js_std_eval_binary_json_module(JSContext *ctx,
const uint8_t *buf, size_t buf_len,
const char *module_name);
void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
JSValueConst reason,
JS_BOOL is_handled, void *opaque);
void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt));
#ifdef __cplusplus
} /* extern "C" { */
#endif
#endif /* QUICKJS_LIBC_H */

View File

@@ -30,7 +30,6 @@
#include <assert.h>
#include <sys/time.h>
#include <time.h>
#include <fenv.h>
#include <math.h>
#if defined(__APPLE__)
#include <malloc/malloc.h>
@@ -66,12 +65,6 @@
#define CONFIG_PRINTF_RNDN
#endif
/* define to include Atomics.* operations which depend on the OS
threads */
#if !defined(EMSCRIPTEN)
#define CONFIG_ATOMICS
#endif
#if !defined(EMSCRIPTEN)
/* enable stack limitation */
#define CONFIG_STACK_CHECK
@@ -108,12 +101,6 @@
/* test the GC by forcing it before each object allocation */
//#define FORCE_GC_AT_MALLOC
#ifdef CONFIG_ATOMICS
#include <pthread.h>
#include <stdatomic.h>
#include <errno.h>
#endif
enum {
/* classid tag */ /* union usage | properties */
JS_CLASS_OBJECT = 1, /* must be first */
@@ -2651,25 +2638,15 @@ static inline BOOL JS_IsEmptyString(JSValueConst v)
/* JSClass support */
#ifdef CONFIG_ATOMICS
static pthread_mutex_t js_class_id_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
/* a new class ID is allocated if *pclass_id != 0 */
JSClassID JS_NewClassID(JSClassID *pclass_id)
{
JSClassID class_id;
#ifdef CONFIG_ATOMICS
pthread_mutex_lock(&js_class_id_mutex);
#endif
class_id = *pclass_id;
if (class_id == 0) {
class_id = js_class_id_alloc++;
*pclass_id = class_id;
}
#ifdef CONFIG_ATOMICS
pthread_mutex_unlock(&js_class_id_mutex);
#endif
return class_id;
}

View File

@@ -405,8 +405,8 @@ void actor_turn(cell_rt *actor)
if (l.type == LETTER_BLOB) {
// Create a JS blob from the C blob
size_t size = l.blob_data->length / 8; // Convert bits to bytes
JSValue arg = js_new_blob_stoned_copy(actor->context, l.blob_data->data, size);
size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes
JSValue arg = js_new_blob_stoned_copy(actor->context, (void *)blob_data(l.blob_data), size);
blob_destroy(l.blob_data);
result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg);
uncaught_exception(actor->context, result);

30
source/setup_playdate.c Normal file
View File

@@ -0,0 +1,30 @@
#include "pd_api.h"
typedef int (PDEventHandler)(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg);
extern PDEventHandler eventHandler;
static void* (*pdrealloc)(void* ptr, size_t size);
int eventHandlerShim(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg)
{
if ( event == kEventInit )
pdrealloc = playdate->system->realloc;
return eventHandler(playdate, event, arg);
}
#if TARGET_PLAYDATE
void* _malloc_r(struct _reent* _REENT, size_t nbytes) { return pdrealloc(NULL,nbytes); }
void* _realloc_r(struct _reent* _REENT, void* ptr, size_t nbytes) { return pdrealloc(ptr,nbytes); }
void _free_r(struct _reent* _REENT, void* ptr ) { if ( ptr != NULL ) pdrealloc(ptr,0); }
#else
void* malloc(size_t nbytes) { return pdrealloc(NULL,nbytes); }
void* realloc(void* ptr, size_t nbytes) { return pdrealloc(ptr,nbytes); }
void free(void* ptr ) { if ( ptr != NULL ) pdrealloc(ptr,0); }
#endif

71
source/stub_playdate.c Normal file
View File

@@ -0,0 +1,71 @@
#include <sys/stat.h>
#include <sys/times.h>
#include <sys/time.h>
#include <errno.h>
#undef errno
extern int errno;
void _exit(int status) {
while(1);
}
int _close(int file) {
return -1;
}
int _fstat(int file, struct stat *st) {
st->st_mode = S_IFCHR;
return 0;
}
int _isatty(int file) {
return 1;
}
int _lseek(int file, int ptr, int dir) {
return 0;
}
int _open(const char *name, int flags, int mode) {
return -1;
}
int _read(int file, char *ptr, int len) {
return 0;
}
void *_sbrk(int incr) {
errno = ENOMEM;
return (void *)-1;
}
int _stat(const char *file, struct stat *st) {
st->st_mode = S_IFCHR;
return 0;
}
int _unlink(const char *name) {
errno = ENOENT;
return -1;
}
int _write(int file, char *ptr, int len) {
return len;
}
int _getpid(void) {
return 1;
}
int _kill(int pid, int sig) {
errno = EINVAL;
return -1;
}
int _gettimeofday(struct timeval *tv, void *tz) {
return 0;
}
void _fini(void) {
}