pxl8/pxl8.sh
2026-01-31 09:31:31 -06:00

638 lines
18 KiB
Bash
Executable file

#!/bin/bash
set -e
CC="${CC:-clang}"
if command -v ccache >/dev/null 2>&1; then
CC="ccache $CC"
fi
CFLAGS="-std=c23 -Wall -Wextra"
LIBS="-lm"
MODE="debug"
BUILDDIR=".build"
BINDIR="bin"
if command -v mold >/dev/null 2>&1; then
LINKER_FLAGS="-fuse-ld=mold"
else
LINKER_FLAGS=""
fi
case "$(uname)" in
Linux)
LINKER_FLAGS="$LINKER_FLAGS -rdynamic"
;;
Darwin)
export MACOSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion | cut -d '.' -f 1)"
;;
MINGW*|MSYS*|CYGWIN*)
LINKER_FLAGS="$LINKER_FLAGS -Wl,--export-all-symbols"
;;
*)
LINKER_FLAGS="$LINKER_FLAGS -rdynamic"
;;
esac
BOLD='\033[1m'
GREEN='\033[38;2;184;187;38m'
NC='\033[0m'
RED='\033[38;2;251;73;52m'
YELLOW='\033[38;2;250;189;47m'
if [[ "$(uname)" == "Linux" ]]; then
CFLAGS="$CFLAGS -D_GNU_SOURCE"
fi
build_luajit() {
if [[ ! -f "lib/luajit/src/libluajit.a" ]]; then
print_info "Building LuaJIT"
cd lib/luajit
make clean >/dev/null 2>&1 || true
make -j$(nproc 2>/dev/null || echo 4) > /dev/null 2>&1
cd - > /dev/null
print_info "Built LuaJIT"
fi
}
build_server() {
local mode="$1"
if [[ -d "pxl8d" ]]; then
print_info "Building pxl8d ($mode mode)"
cd pxl8d
if [[ "$mode" == "release" ]]; then
cargo build --release --quiet
else
cargo build --quiet
fi
local status=$?
cd - > /dev/null
if [[ $status -eq 0 ]]; then
print_info "Built pxl8d"
else
print_error "pxl8d build failed"
fi
fi
}
start_server() {
local mode="$1"
local server_bin
if [[ "$mode" == "release" ]]; then
server_bin="pxl8d/target/release/pxl8d"
else
server_bin="pxl8d/target/debug/pxl8d"
fi
print_info "Server mode: $mode, binary: $server_bin"
if [[ -f "$server_bin" ]]; then
print_info "Starting server..."
./$server_bin &
SERVER_PID=$!
print_info "Server started with PID $SERVER_PID"
sleep 0.5
else
print_error "pxl8d binary not found: $server_bin"
print_error "Build pxl8d first with: cd pxl8d && cargo build"
fi
}
stop_server() {
if [[ -n "$SERVER_PID" ]]; then
print_info "Stopping server"
kill $SERVER_PID 2>/dev/null || true
wait $SERVER_PID 2>/dev/null || true
fi
}
clean_server() {
if [[ -d "pxl8d" ]]; then
print_info "Cleaning pxl8d"
cd pxl8d
cargo clean 2>/dev/null || true
cd - > /dev/null
fi
}
build_sdl() {
if [[ ! -f "lib/SDL/build/libSDL3.so" ]] && [[ ! -f "lib/SDL/build/libSDL3.a" ]] && [[ ! -f "lib/SDL/build/libSDL3.dylib" ]]; then
print_info "Building SDL3"
mkdir -p lib/SDL/build
cd lib/SDL/build
cmake .. -DCMAKE_BUILD_TYPE=Release > /dev/null 2>&1
cmake --build . --parallel $(nproc 2>/dev/null || echo 4) > /dev/null 2>&1
cd - > /dev/null
print_info "Built SDL3"
fi
}
prefix_output() {
while IFS= read -r line; do
if [[ "$line" == *": warning:"* ]] || [[ "$line" == *": note:"* ]]; then
echo -e "${YELLOW}${BOLD}[$(timestamp) WARN]${NC} $line" >&2
else
echo -e "${RED}${BOLD}[$(timestamp) ERROR]${NC} $line" >&2
fi
done
}
compile_source_file() {
local src_file="$1"
local obj_file="$2"
local compile_flags="$3"
print_info "Compiling: $src_file"
local output
local exit_code
output=$($CC -c $compile_flags "$src_file" -o "$obj_file" 2>&1) || exit_code=$?
if [[ -n "$output" ]]; then
echo "$output" | prefix_output
fi
if [[ -n "$exit_code" ]]; then
print_error "Compilation failed for $src_file"
exit 1
fi
}
make_lib_dirs() {
mkdir -p lib/linenoise lib/fennel lib/miniz
}
print_error() {
echo -e "${RED}${BOLD}[$(timestamp) ERROR]${NC} $1" >&2
}
print_info() {
echo -e "${GREEN}${BOLD}[$(timestamp) INFO]${NC} $1"
}
print_usage() {
echo -e "${BOLD}pxl8${NC} - framework build tool"
echo
echo -e "${BOLD}USAGE:${NC}"
echo " ./pxl8.sh <command> [options]"
echo
echo -e "${BOLD}COMMANDS:${NC}"
echo " ase Aseprite tools (use 'ase help' for subcommands)"
echo " build Build pxl8"
echo " clean Remove build artifacts"
echo " help Show this help message"
echo " install Install pxl8 to ~/.local/bin"
echo " run Build and run pxl8 (optional: cart.pxc or folder)"
echo " update Download/update all dependencies"
echo " vendor Fetch source for dependencies (ex. SDL3)"
echo
echo -e "${BOLD}OPTIONS:${NC}"
echo " --all Clean both build artifacts and dependencies"
echo " --cache Clear ccache (use with clean)"
echo " --deps Clean only dependencies"
echo " --release Build/run/clean in release mode (default: debug)"
}
setup_sdl3() {
if [[ -d "lib/SDL/build" ]]; then
SDL3_CFLAGS="-Ilib/SDL/include"
SDL3_LIBS="-Llib/SDL/build -lSDL3"
SDL3_RPATH="-Wl,-rpath,$(pwd)/lib/SDL/build"
print_info "Using vendored SDL3"
else
SDL3_CFLAGS=$(pkg-config --cflags sdl3 2>/dev/null || echo "")
SDL3_LIBS=$(pkg-config --libs sdl3 2>/dev/null || echo "")
SDL3_RPATH=""
if [[ -z "$SDL3_LIBS" ]]; then
case "$(uname)" in
Darwin)
if [[ -f "/usr/local/include/SDL3/SDL.h" ]]; then
SDL3_CFLAGS="-I/usr/local/include/SDL3"
SDL3_LIBS="-L/usr/local/lib -lSDL3"
fi
;;
Linux)
if [[ -f "/usr/include/SDL3/SDL.h" ]]; then
SDL3_CFLAGS="-I/usr/include/SDL3"
SDL3_LIBS="-lSDL3"
fi
;;
MINGW*|MSYS*|CYGWIN*)
if [[ -f "/mingw64/include/SDL3/SDL.h" ]]; then
SDL3_CFLAGS="-I/mingw64/include/SDL3"
SDL3_LIBS="-lSDL3"
fi
;;
esac
fi
if [[ -z "$SDL3_LIBS" ]]; then
print_info "System SDL3 not found, vendoring SDL3..."
update_sdl
build_sdl
SDL3_CFLAGS="-Ilib/SDL/include"
SDL3_LIBS="-Llib/SDL/build -lSDL3"
SDL3_RPATH="-Wl,-rpath,$(pwd)/lib/SDL/build"
print_info "Using vendored SDL3"
else
print_info "Using system SDL3"
fi
fi
CFLAGS="$CFLAGS $SDL3_CFLAGS"
LIBS="$LIBS $SDL3_LIBS $SDL3_RPATH"
}
timestamp() {
date +"%H:%M:%S"
}
update_fennel() {
print_info "Fetching Fennel"
local version="1.6.1"
if curl -sL --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then
if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then
print_info "Updated Fennel (${version})"
else
print_error "Downloaded file is empty or missing"
return 1
fi
else
print_error "Failed to download Fennel"
return 1
fi
}
update_linenoise() {
print_info "Fetching linenoise"
if curl -sL --max-time 5 -o lib/linenoise/linenoise.c https://raw.githubusercontent.com/antirez/linenoise/master/linenoise.c 2>/dev/null && \
curl -sL --max-time 5 -o lib/linenoise/linenoise.h https://raw.githubusercontent.com/antirez/linenoise/master/linenoise.h 2>/dev/null; then
print_info "Updated linenoise"
else
print_error "Failed to download linenoise"
return 1
fi
}
update_luajit() {
print_info "Fetching LuaJIT"
local version="2.1"
local branch="v${version}"
if [[ -d "lib/luajit/.git" ]]; then
cd lib/luajit && git pull --quiet origin ${branch}
cd - > /dev/null
else
rm -rf lib/luajit
git clone --quiet --branch ${branch} https://github.com/LuaJIT/LuaJIT.git lib/luajit
fi
print_info "Updated LuaJIT (${version})"
}
update_miniz() {
print_info "Fetching miniz"
local version="3.1.0"
if curl -sL --max-time 5 -o /tmp/miniz.zip "https://github.com/richgel999/miniz/releases/download/${version}/miniz-${version}.zip" 2>/dev/null; then
unzip -qjo /tmp/miniz.zip miniz.c miniz.h -d lib/miniz/ 2>/dev/null
rm -f /tmp/miniz.zip
if [[ -f "lib/miniz/miniz.c" ]] && [[ -f "lib/miniz/miniz.h" ]]; then
print_info "Updated miniz (${version})"
else
print_error "Failed to extract miniz files"
return 1
fi
else
print_error "Failed to download miniz"
return 1
fi
}
update_sdl() {
print_info "Fetching SDL3"
if [[ -d "lib/SDL/.git" ]]; then
cd lib/SDL && git pull --quiet origin main
cd - > /dev/null
else
rm -rf lib/SDL
git clone --quiet https://github.com/libsdl-org/SDL.git lib/SDL
fi
print_info "Updated SDL3"
}
COMMAND="$1"
shift || true
for arg in "$@"; do
case $arg in
--release)
MODE="release"
;;
esac
done
if [ "$MODE" = "release" ]; then
CFLAGS="$CFLAGS -O3 -flto -ffast-math -funroll-loops -fno-unwind-tables -fno-asynchronous-unwind-tables"
LINKER_FLAGS="$LINKER_FLAGS -flto"
BUILDDIR="$BUILDDIR/release"
BINDIR="$BINDIR/release"
else
CFLAGS="$CFLAGS -g -O1 -DDEBUG"
BUILDDIR="$BUILDDIR/debug"
BINDIR="$BINDIR/debug"
fi
DEP_CFLAGS="-O3 -funroll-loops"
case "$COMMAND" in
build)
mkdir -p "$BUILDDIR"
mkdir -p "$BINDIR"
if [[ ! -d "lib/luajit" ]] || \
[[ ! -f "lib/linenoise/linenoise.c" ]] || \
[[ ! -f "lib/miniz/miniz.c" ]] || \
[[ ! -f "lib/fennel/fennel.lua" ]]; then
print_info "Missing dependencies, fetching..."
make_lib_dirs
update_fennel
update_linenoise
update_luajit
update_miniz
fi
if [[ ! -f "bin/.gitignore" ]]; then
echo "*" > "bin/.gitignore"
fi
if [[ ! -f ".build/.gitignore" ]]; then
echo "*" > ".build/.gitignore"
fi
if [[ ! -f "lib/.gitignore" ]]; then
echo "*" > "lib/.gitignore"
fi
if [[ -d "lib/luajit" ]]; then
build_luajit
fi
if [[ -d "lib/SDL" ]]; then
build_sdl
fi
build_server "$MODE"
setup_sdl3
print_info "Building pxl8 ($MODE mode)"
if [[ "$CC" == ccache* ]]; then
print_info "Compiler cache: ccache enabled"
fi
INCLUDES="-Isrc/asset -Isrc/bsp -Isrc/core -Isrc/gfx -Isrc/gui -Isrc/hal -Isrc/math -Isrc/net -Isrc/procgen -Isrc/script -Isrc/sfx -Isrc/sim -Isrc/vxl -Isrc/world -Ilib/linenoise -Ilib/luajit/src -Ilib/miniz"
COMPILE_FLAGS="$CFLAGS $INCLUDES"
DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES"
EXECUTABLE="$BINDIR/pxl8"
LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c"
PXL8_SOURCE_FILES="
src/asset/pxl8_ase.c
src/asset/pxl8_cart.c
src/asset/pxl8_save.c
src/bsp/pxl8_bsp.c
src/bsp/pxl8_bsp_render.c
src/core/pxl8.c
src/core/pxl8_bytes.c
src/core/pxl8_io.c
src/core/pxl8_log.c
src/core/pxl8_replay.c
src/core/pxl8_rng.c
src/gfx/pxl8_3d_camera.c
src/gfx/pxl8_anim.c
src/gfx/pxl8_atlas.c
src/gfx/pxl8_blit.c
src/gfx/pxl8_colormap.c
src/gfx/pxl8_cpu.c
src/gfx/pxl8_dither.c
src/gfx/pxl8_font.c
src/gfx/pxl8_gfx.c
src/gfx/pxl8_glows.c
src/gfx/pxl8_lightmap.c
src/gfx/pxl8_lights.c
src/gfx/pxl8_mesh.c
src/gfx/pxl8_palette.c
src/gfx/pxl8_particles.c
src/gfx/pxl8_tilemap.c
src/gfx/pxl8_tilesheet.c
src/gfx/pxl8_transition.c
src/gui/pxl8_gui.c
src/hal/pxl8_hal_sdl3.c
src/hal/pxl8_mem_sdl3.c
src/hal/pxl8_thread_sdl3.c
src/math/pxl8_math.c
src/math/pxl8_noise.c
src/net/pxl8_net.c
src/net/pxl8_protocol.c
src/procgen/pxl8_graph.c
src/script/pxl8_repl.c
src/script/pxl8_script.c
src/sfx/pxl8_sfx.c
src/sim/pxl8_sim.c
src/vxl/pxl8_vxl.c
src/vxl/pxl8_vxl_render.c
src/world/pxl8_entity.c
src/world/pxl8_world.c
src/world/pxl8_world_chunk.c
src/world/pxl8_world_chunk_cache.c
"
LUAJIT_LIB="lib/luajit/src/libluajit.a"
OBJECT_DIR="$BUILDDIR/obj"
mkdir -p "$OBJECT_DIR"
OBJECTS=""
NEED_LINK=false
SOURCES_COMPILED=""
for src_file in $LIB_SOURCE_FILES; do
obj_name=$(basename "$src_file" .c).o
obj_file="$OBJECT_DIR/$obj_name"
OBJECTS="$OBJECTS $obj_file"
if [[ "$src_file" -nt "$obj_file" ]]; then
NEED_LINK=true
compile_source_file "$src_file" "$obj_file" "$DEP_COMPILE_FLAGS"
SOURCES_COMPILED="yes"
fi
done
for src_file in $PXL8_SOURCE_FILES; do
obj_name=$(basename "$src_file" .c).o
obj_file="$OBJECT_DIR/$obj_name"
OBJECTS="$OBJECTS $obj_file"
NEEDS_REBUILD=false
if [[ "$src_file" -nt "$obj_file" ]] || \
[[ "src/core/pxl8_types.h" -nt "$obj_file" ]] || \
[[ "src/core/pxl8_macros.h" -nt "$obj_file" ]] || \
[[ "src/net/pxl8_protocol.h" -nt "$obj_file" ]]; then
NEEDS_REBUILD=true
fi
if [[ "$src_file" == "src/script/pxl8_script.c" ]]; then
for lua_file in src/lua/*.lua src/lua/pxl8/*.lua lib/fennel/fennel.lua; do
if [[ -f "$lua_file" ]] && [[ "$lua_file" -nt "$obj_file" ]]; then
NEEDS_REBUILD=true
break
fi
done
fi
if [[ "$NEEDS_REBUILD" == true ]]; then
NEED_LINK=true
compile_source_file "$src_file" "$obj_file" "$COMPILE_FLAGS"
SOURCES_COMPILED="yes"
fi
done
if [[ "$LUAJIT_LIB" -nt "$EXECUTABLE" ]] || [[ "$NEED_LINK" == true ]]; then
print_info "Linking executable"
if ! $CC $LINKER_FLAGS $OBJECTS $LUAJIT_LIB $LIBS -o "$EXECUTABLE"; then
print_error "Linking failed"
exit 1
fi
elif [[ -z "$SOURCES_COMPILED" ]]; then
print_info "All files are up to date, skipping build"
fi
print_info "Build complete"
print_info "Binary: $EXECUTABLE"
;;
run)
"$0" build "$@" || exit 1
CART=""
EXTRA_ARGS=""
for arg in "$@"; do
if [[ "$arg" == "--release" ]]; then
MODE="release"
elif [[ "$arg" == "--repl" ]]; then
EXTRA_ARGS="$EXTRA_ARGS --repl"
elif [[ -z "$CART" ]]; then
CART="$arg"
fi
done
start_server "$MODE"
trap stop_server EXIT
if [[ -z "$CART" ]]; then
"$BINDIR/pxl8" $EXTRA_ARGS
else
"$BINDIR/pxl8" "$CART" $EXTRA_ARGS
fi
;;
clean)
CLEAN_ALL=false
CLEAN_CACHE=false
CLEAN_DEPS=false
CLEAN_RELEASE=false
for arg in "$@"; do
case "$arg" in
--all) CLEAN_ALL=true ;;
--cache) CLEAN_CACHE=true ;;
--deps) CLEAN_DEPS=true ;;
--release) CLEAN_RELEASE=true ;;
esac
done
if [[ "$CLEAN_CACHE" == true ]] && command -v ccache >/dev/null 2>&1; then
print_info "Clearing ccache"
ccache -C >/dev/null
fi
if [[ "$CLEAN_RELEASE" == true ]]; then
BUILD_PATH=".build/release"
BIN_PATH="bin/release"
MODE="release"
else
BUILD_PATH=".build/debug"
BIN_PATH="bin/debug"
MODE="debug"
fi
if [[ "$CLEAN_ALL" == true ]]; then
print_info "Removing build artifacts and dependencies"
rm -rf "$BUILD_PATH" "$BIN_PATH" lib
clean_server
print_info "Cleaned all"
elif [[ "$CLEAN_DEPS" == true ]]; then
print_info "Removing dependencies"
rm -rf lib
print_info "Cleaned dependencies"
else
print_info "Removing build artifacts"
rm -rf "$BUILD_PATH" "$BIN_PATH"
clean_server
print_info "Cleaned"
fi
;;
update)
make_lib_dirs
update_fennel
update_linenoise
update_luajit
update_miniz
print_info "All dependencies updated"
;;
vendor)
update_sdl
;;
install)
"$0" build --release || exit 1
INSTALL_DIR="${HOME}/.local/bin"
mkdir -p "$INSTALL_DIR"
cp "bin/release/pxl8" "$INSTALL_DIR/pxl8"
chmod +x "$INSTALL_DIR/pxl8"
print_info "Installed pxl8 to $INSTALL_DIR/pxl8"
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
print_info "Note: Add $INSTALL_DIR to your PATH if not already"
fi
;;
ase)
bash tools/aseprite/pxl8-ase.sh "$@"
;;
help|--help|-h|"")
print_usage
;;
*)
print_error "Unknown command: $COMMAND"
print_usage
exit 1
;;
esac