# hosts/wasm/Makefile — undoom browser host: lumbda's C tier + wasm_host.c
# compiled with emscripten. Run from the undoom repo root:
#   make -C hosts/wasm            build dist/ (page + worker + wasm + data)
#   make -C hosts/wasm test       node Worker smoke test: TITLEPIC adler32 (tree, --fast, --jit)
#   make -C hosts/wasm bench      tier timings (tree-walker, --fast, --jit)
#   make -C hosts/wasm bench-play E1M1 play sim + renderer, ms per tic+frame (--jit)
#   make -C hosts/wasm test-browser  headless Chromium via playwright-core (optional)
#   make -C hosts/wasm serve      COOP/COEP static server on 127.0.0.1:8080
#
# Flags follow lumbda's wasm/Makefile C tier. Boehm GC comes from lumbda's
# wasm/gc.mk: bdwgc built with emcc, wasm locals spilled with Asyncify at
# each collection. lumbda_load (embed.c) runs the game inside
# lumbda_gc_drive, so wasm_host.c needs no GC code. The JIT is lumbda's
# WebAssembly backend, c/jit_wasm.c (x86_64 jit.c cannot run in a wasm
# engine): it compiles procedures to wasm modules at run time & appends
# them to the function table, hence ALLOW_TABLE_GROWTH. Those modules
# carry no Asyncify instrumentation, so lumbda collects only while no
# compiled frame is live (c/gc.c). A lumbda checkout without jit_wasm.c
# falls back to our jit-stub.c (--jit then does nothing).
# LUMBDA_WASM_GC=0 builds the old never-free variant for comparison.

UNDOOM_ROOT := $(abspath ../..)
LUMBDA_DIR  ?= $(HOME)/git/lumbda
EMSDK_DIR   ?= $(HOME)/git/emsdk
EMCC        := $(EMSDK_DIR)/upstream/emscripten/emcc
EMSDK_NODE_DIR := $(firstword $(wildcard $(EMSDK_DIR)/node/*/bin))
EMENV       := EMSDK=$(EMSDK_DIR) EMSDK_NODE=$(EMSDK_NODE_DIR)/node \
               PATH=$(EMSDK_NODE_DIR):$(EMSDK_DIR)/upstream/emscripten:$$PATH
NICE        := nice -n 10
NODE        ?= node
WAD         ?= $(UNDOOM_ROOT)/wad/freedoom1.wad
FRAMES      ?= 3

DIST := dist

include $(LUMBDA_DIR)/wasm/gc.mk

JIT_C    ?= $(or $(wildcard $(LUMBDA_DIR)/c/jit_wasm.c),jit-stub.c)
LUMBDA_C := $(addprefix $(LUMBDA_DIR)/c/, \
	reader.c printer.c eval.c vm.c types.c builtins.c bytevector.c bignum.c portal.c embed.c) \
	$(LUMBDA_GC_SRC) $(JIT_C)
LUMBDA_H := $(wildcard $(LUMBDA_DIR)/c/*.h)

# lumbda's error handling is setjmp/longjmp. emscripten's default (JS)
# sjlj routes every call out of a setjmp-bearing function (leval, the VM)
# through JS invoke_* trampolines; wasm exception handling avoids that.
SJLJ    ?= -sSUPPORT_LONGJMP=wasm
OPT     ?= -O2
CFLAGS  := $(OPT) $(SJLJ) -std=c11 -D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE \
           -I$(LUMBDA_DIR)/c -Wno-everything $(LUMBDA_GC_CFLAGS)
LDFLAGS := $(OPT) $(SJLJ) -s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME=createUndoom \
           -s INVOKE_RUN=0 -s EXIT_RUNTIME=0 \
           -s EXPORTED_RUNTIME_METHODS='["callMain","FS","ENV","HEAPU8"]' \
           -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=64MB -s MAXIMUM_MEMORY=4GB \
           -s ALLOW_TABLE_GROWTH=1 \
           -s STACK_SIZE=16MB \
           -s ENVIRONMENT=worker,node \
           --js-library library_undoom.js $(LUMBDA_GC_LDFLAGS)

# Scheme sources packed into the module's FS (.data file). WAD is not:
# the page fetches it at runtime and worker.mjs writes it into the FS.
SRC_LSP := $(shell find $(UNDOOM_ROOT)/src -name '*.lsp') $(wildcard *.lsp) ../boot.lsp
PRELOAD := --preload-file $(UNDOOM_ROOT)/src@/undoom/src \
           $(foreach f,$(wildcard *.lsp),--preload-file $(f)@/undoom/hosts/wasm/$(f)) \
           --preload-file ../boot.lsp@/undoom/hosts/boot.lsp \
           --preload-file $(LUMBDA_DIR)/stdlib.lsp@/lumbda/stdlib.lsp

PAGE := index.html main.mjs worker.mjs shared.mjs audio.mjs coi-sw.js touch.mjs gamepad.mjs loading.mjs help.mjs

.PHONY: all build test test-browser bench bench-play serve publish clean $(DIST)/undoom-src.tgz

all: build
build: $(DIST)/undoom.js $(addprefix $(DIST)/,$(PAGE)) $(DIST)/build.txt $(DIST)/undoom-src.tgz \
       $(DIST)/SHA256SUMS

# GPLv2: the page links the exact source it was built from (committed HEAD),
# with its commit, MD5 & SHA-256 (sha256sum -c / md5sum -c format) so
# anyone can verify the download. git archive is deterministic per commit.
$(DIST)/undoom-src.tgz:
	@mkdir -p $(DIST)
	git -C $(UNDOOM_ROOT) archive --prefix=undoom/ -o $(abspath $@) HEAD
	git -C $(UNDOOM_ROOT) rev-parse HEAD > $(DIST)/undoom-src.commit
	cd $(DIST) && md5sum undoom-src.tgz > undoom-src.tgz.md5 && sha256sum undoom-src.tgz > undoom-src.tgz.sha256

$(DIST)/undoom.js: Makefile wasm_host.c library_undoom.js $(LUMBDA_C) $(LUMBDA_H) $(SRC_LSP) $(LUMBDA_GC_DEP)
	@mkdir -p $(DIST)
	$(EMENV) timeout 900 $(NICE) $(EMCC) $(CFLAGS) $(LUMBDA_C) wasm_host.c \
	  $(LDFLAGS) $(PRELOAD) -o $@

$(DIST)/%: %
	@mkdir -p $(DIST)
	cp $< $@

# Build id: hash of everything a deploy ships. index.html loads main.mjs
# with it & main.mjs passes it on to every worker file (worker.mjs).
$(DIST)/build.txt: $(DIST)/undoom.js $(addprefix $(DIST)/,$(filter-out index.html,$(PAGE)))
	cat $^ $(DIST)/undoom.wasm $(DIST)/undoom.data | sha256sum | cut -c1-12 > $@
$(DIST)/index.html: index.html $(DIST)/build.txt $(DIST)/undoom-src.tgz
	sed -e 's|src="main.mjs"|src="main.mjs?v=$(shell cat $(DIST)/build.txt)"|' \
	    -e "s|@SRC_COMMIT@|$$(cut -c1-12 $(DIST)/undoom-src.commit)|" \
	    -e "s|@SRC_MD5@|$$(cut -d' ' -f1 $(DIST)/undoom-src.tgz.md5)|" \
	    -e "s|@SRC_SHA256@|$$(cut -d' ' -f1 $(DIST)/undoom-src.tgz.sha256)|" $< > $@

# Checksums of every file a deploy ships (the WAD comes from Freedoom).
SHIPPED := undoom.js undoom.wasm undoom.data undoom-src.tgz build.txt $(PAGE)
$(DIST)/SHA256SUMS $(DIST)/MD5SUMS: $(addprefix $(DIST)/,$(SHIPPED))
	cd $(DIST) && sha256sum $(SHIPPED) > SHA256SUMS && md5sum $(SHIPPED) > MD5SUMS

# Every evaluator: tree-walker, --fast (auto-compile to bytecode VM), --jit
# (WebAssembly JIT, the page's default). The --fast run also presses Escape
# through the ring.
test: build
	@mkdir -p $(UNDOOM_ROOT)/out
	timeout 600 $(NICE) $(NODE) test-node.mjs --wad $(WAD) --frames $(FRAMES) \
	  --ppm $(UNDOOM_ROOT)/out/wasm-title.ppm
	timeout 600 $(NICE) $(NODE) test-node.mjs --wad $(WAD) --fast --escape
	timeout 600 $(NICE) $(NODE) test-node.mjs --wad $(WAD) --jit --frames $(FRAMES)
	timeout 600 $(NICE) $(NODE) test-node.mjs --wad $(WAD) --fast --frames 0 \
	  --script hosts/wasm/boot-sound.lsp | tee /dev/stderr | grep -q '^sounds=1 '

# Tier timings: load, WAD init, TITLEPIC draws, adler32 (both evaluators).
bench: build
	timeout 600 $(NICE) $(NODE) test-node.mjs --wad $(WAD) --script hosts/wasm/bench-draw.lsp
	timeout 600 $(NICE) $(NODE) test-node.mjs --wad $(WAD) --fast --script hosts/wasm/bench-draw.lsp
	timeout 600 $(NICE) $(NODE) test-node.mjs --wad $(WAD) --jit --script hosts/wasm/bench-draw.lsp

# Play timing: E1M1 (-warp 1 1 -skill 3), TICS singletics tics, each running
# the play sim & drawing a frame; prints ms per tic+frame (boot-play.lsp).
TICS ?= 700
bench-play: build
	timeout 900 $(NICE) $(NODE) test-node.mjs --wad $(WAD) --jit --frames 0 \
	  --script hosts/wasm/boot-play.lsp --args "$(TICS)"

test-browser: build
	timeout 300 $(NICE) $(NODE) test-browser.mjs 8093 $(UNDOOM_ROOT)/out/wasm-browser.png

serve: build
	@test -e $(DIST)/freedoom1.wad || ln -s $(WAD) $(DIST)/freedoom1.wad
	python3 -u serve-coop.py 8080 $(DIST)

# Publish the page to lumbda.com: copy dist/ (minus the WAD, which never
# enters git) into lumbda's www/doom/. lumbda's CI fetches Freedoom there
# at deploy time. publish-sums.py lists the source checksums on lumbda's
# homepage. Commit & push from $(LUMBDA_DIR).
LUMBDA_WWW_DOOM ?= $(LUMBDA_DIR)/www/doom
publish: build
	@git -C $(UNDOOM_ROOT) diff --quiet HEAD || { echo "publish: commit undoom first (source tarball = HEAD)"; exit 1; }
	mkdir -p $(LUMBDA_WWW_DOOM)
	cp $(addprefix $(DIST)/,$(SHIPPED)) $(DIST)/undoom-src.tgz.md5 $(DIST)/undoom-src.tgz.sha256 \
	   $(DIST)/SHA256SUMS $(DIST)/MD5SUMS $(LUMBDA_WWW_DOOM)/
	python3 publish-sums.py $(LUMBDA_DIR)/www/index.html $(DIST)

clean:
	rm -rf $(DIST)
