playdate support
This commit is contained in:
@@ -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++"
|
||||
82
meson.build
82
meson.build
@@ -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
319
scripts/build.ce
Normal 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
471
scripts/build.cm
Normal 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
9
scripts/enet_playdate.c
Normal 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);
|
||||
}
|
||||
@@ -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
248
scripts/http_playdate.c
Normal 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;
|
||||
}
|
||||
12
scripts/os.c
12
scripts/os.c
@@ -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
178
scripts/os_playdate.c
Normal 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;
|
||||
}
|
||||
@@ -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
115
scripts/socket_playdate.c
Normal 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
75
scripts/time_playdate.c
Normal 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;
|
||||
}
|
||||
@@ -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
49
source/main_playdate.c
Normal 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
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
30
source/setup_playdate.c
Normal 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
71
source/stub_playdate.c
Normal 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) {
|
||||
}
|
||||
Reference in New Issue
Block a user