Compare commits
5
Commits
7e3bc38da4
...
321102cc72
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
321102cc72 | ||
|
|
948e16cf16 | ||
|
|
994c1a00ab | ||
|
|
6973c517b4 | ||
|
|
166145631b |
No files matched your search
+10
-3
@@ -318,8 +318,12 @@ fetches) to the server, so the game talks to its own origin; the server stays un
|
||||
(IndexedDB), X-MM-User sent as on desktop (the cookie works too, same origin).
|
||||
Fullscreen (F11, display dialog) through the browser's Fullscreen API, not saved or
|
||||
restored (the browser allows it only after a click or key). Moon generation is plain
|
||||
Lua there (no LuaJIT): grid capped at 96 (`WEB_RESOLUTION`) and 12 ms a frame, a few
|
||||
seconds per moon. Moon targets fall back to 8 bits without EXT_color_buffer_float.
|
||||
Lua there (no LuaJIT): grid capped at 96 (`WEB_RESOLUTION`) and 8 ms a frame (keeps
|
||||
60 fps; a checkpoint every few ms of work in terrain.lua), about 4 s per moon. Moon targets fall back to 8 bits without EXT_color_buffer_float.
|
||||
Variable frame rate there (`sys.set_update_frequency(0)` in main.script): the browser's
|
||||
requestAnimationFrame paces at the display's rate; `display.update_frequency = 60`
|
||||
(desktop's timer pacing) made the engine skip callbacks: 60 fps in a 2/3 cadence on
|
||||
120/144 Hz displays, dropped frames on jittery 60 Hz ones. Use dt or the clock for motion.
|
||||
- WebGL: every sampler of a bound program must not be the framebuffer being drawn
|
||||
("feedback loop", the draw is dropped), and Firefox aborts on resizing a target with a
|
||||
depth buffer: the render script binds every bloom sampler and makes targets again at a
|
||||
@@ -377,7 +381,10 @@ The game it starts saves its files under `<out>/data` (XDG_DATA_HOME, or
|
||||
`g`, `st`, `view`, `busy`, `el(id)`), `idle()` (no request in flight, built),
|
||||
`eval(expr)`, `elements()`, `state()`, `set("reduced_motion", "1")`,
|
||||
`set("scroll:<key>", px)`, `shot(name)`, `log_has(regex)`, `expect(cond, msg)`,
|
||||
`restart()` (quit and start the game again, same options and saved files).
|
||||
`restart()` (quit and start the game again, same options and saved files),
|
||||
`cmd("frames", "reset")` (frame times since the last reset: fps, dt, the game screen's
|
||||
update and its rebuilds in ms; the web harness reads it the same way). F3 in a debug
|
||||
build shows the engine's profiler.
|
||||
Those go through the test hook, which calls the screen's input function directly, so
|
||||
they work even when the engine sends the game no input. Real input through the X server
|
||||
(xdotool on the driver's Xvfb; key and button held 80 ms, since the engine polls once a
|
||||
|
||||
@@ -26,6 +26,13 @@ end
|
||||
|
||||
function init(self)
|
||||
print("mm: api " .. api.base .. ", player " .. (api.player_id() or "none yet"))
|
||||
-- In a browser the frames come from requestAnimationFrame at the display's rate; with
|
||||
-- display.update_frequency set the engine skips callbacks to approach it (judder on
|
||||
-- 120/144 Hz displays, dropped frames on jittery 60 Hz ones). Variable rate there:
|
||||
-- every callback is a frame, dt is the real time.
|
||||
if html5 then
|
||||
sys.set_update_frequency(0)
|
||||
end
|
||||
local w, h = sys.get_config_string("mm.window", ""):match("^(%d+)x(%d+)$")
|
||||
if w then
|
||||
msg.post("@render:", "resize", { width = tonumber(w), height = tonumber(h) })
|
||||
|
||||
@@ -424,7 +424,13 @@ local FACES = {
|
||||
|
||||
-- Unit-sphere directions of the cube-sphere grid (6 faces of (n+1)^2 vertices, flat xyz,
|
||||
-- vertex v at [3v+1..3v+3], v 0-based) and the shared key of each seam vertex (-1 inside).
|
||||
-- (Read-only for callers: cached per n, like the strip.)
|
||||
local sphere_cache = {}
|
||||
function M.cube_sphere(n)
|
||||
local hit = sphere_cache[n]
|
||||
if hit then
|
||||
return hit[1], hit[2]
|
||||
end
|
||||
local row = n + 1
|
||||
local dirs, keys = {}, {}
|
||||
local v = 0
|
||||
@@ -453,6 +459,7 @@ function M.cube_sphere(n)
|
||||
end
|
||||
end
|
||||
end
|
||||
sphere_cache[n] = { dirs, keys }
|
||||
return dirs, keys
|
||||
end
|
||||
|
||||
@@ -539,6 +546,7 @@ function M.generate(p, checkpoint)
|
||||
local per = row * row
|
||||
local count = 6 * per
|
||||
local dirs, keys = M.cube_sphere(n)
|
||||
checkpoint()
|
||||
local heights, ejecta = {}, {}
|
||||
|
||||
local technique = p.technique
|
||||
@@ -553,6 +561,7 @@ function M.generate(p, checkpoint)
|
||||
cparams.maxRadius = cr.maxRadius * 0.6
|
||||
end
|
||||
local craters = make_craters(cparams, p.craterSeed or 1, crater_field and 3 or 1)
|
||||
checkpoint()
|
||||
local x_max = craters.x_max
|
||||
local shape_strength = crater_field and sh.strength * 0.4 or sh.strength
|
||||
local smooth = max(0, cr.smoothness)
|
||||
@@ -615,7 +624,7 @@ function M.generate(p, checkpoint)
|
||||
end
|
||||
heights[i + 1] = h + crater_h * cr.depth
|
||||
ejecta[i + 1] = min(1, glow)
|
||||
if i % 256 == 0 then
|
||||
if i % 64 == 0 then
|
||||
checkpoint()
|
||||
end
|
||||
end
|
||||
@@ -624,6 +633,9 @@ function M.generate(p, checkpoint)
|
||||
local mean = 0
|
||||
for i = 1, count do
|
||||
mean = mean + heights[i]
|
||||
if i % 4096 == 0 then
|
||||
checkpoint()
|
||||
end
|
||||
end
|
||||
mean = mean / count
|
||||
local positions = {}
|
||||
@@ -641,6 +653,9 @@ function M.generate(p, checkpoint)
|
||||
if hh > max_h then
|
||||
max_h = hh
|
||||
end
|
||||
if i % 2048 == 0 then
|
||||
checkpoint()
|
||||
end
|
||||
end
|
||||
checkpoint()
|
||||
|
||||
@@ -648,6 +663,9 @@ function M.generate(p, checkpoint)
|
||||
local normals = {}
|
||||
for i = 1, count * 3 do
|
||||
normals[i] = 0
|
||||
if i % 8192 == 0 then
|
||||
checkpoint()
|
||||
end
|
||||
end
|
||||
local P = positions
|
||||
local function tri(a, b, c)
|
||||
@@ -690,7 +708,11 @@ function M.generate(p, checkpoint)
|
||||
seams[key] = { i }
|
||||
end
|
||||
end
|
||||
if i % 4096 == 0 then
|
||||
checkpoint()
|
||||
end
|
||||
end
|
||||
checkpoint()
|
||||
for _, group in pairs(seams) do
|
||||
if #group > 1 then
|
||||
local sx, sy, sz = 0, 0, 0
|
||||
@@ -710,6 +732,9 @@ function M.generate(p, checkpoint)
|
||||
len = 1
|
||||
end
|
||||
normals[o + 1], normals[o + 2], normals[o + 3] = nx / len, ny / len, nz / len
|
||||
if i % 2048 == 0 then
|
||||
checkpoint()
|
||||
end
|
||||
end
|
||||
checkpoint()
|
||||
|
||||
@@ -755,7 +780,7 @@ function M.generate(p, checkpoint)
|
||||
|
||||
local gr = 1 + fbm(x + ox, y + oy, z + oz, 2, 14, 0.5, 2) * grain
|
||||
colors[o + 1], colors[o + 2], colors[o + 3] = c1 * gr, c2 * gr, c3 * gr
|
||||
if i % 512 == 0 then
|
||||
if i % 128 == 0 then
|
||||
checkpoint()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
-- state a summary of the shown state (view, wallet, counts, busy)
|
||||
-- set <k> <v> settings: reduced_motion, theme (dark|light), scroll:<key>
|
||||
-- mark <name> print "mm: shot <name>" (scripts/shot.sh SHOTS_DIR picks it up)
|
||||
-- frames [reset] frame times since the last reset (the last 600 frames): fps, dt,
|
||||
-- the game screen's update and its rebuilds, in ms (n, mean, p50,
|
||||
-- p95, max); "reset" starts over
|
||||
|
||||
local focus = require("modules.focus")
|
||||
|
||||
@@ -427,6 +430,64 @@ function COMMANDS.set(g, cmd, _, api)
|
||||
return { ok = true }
|
||||
end
|
||||
|
||||
-- Frame times (the frames command): the engine's dt, the game screen's update time and
|
||||
-- the rebuilds' time, recorded by M.frame every frame (core/main.lua) with M.clock (on the
|
||||
-- web the page's performance.now: socket.gettime has whole milliseconds there).
|
||||
local FRAMES = 600
|
||||
local frames
|
||||
|
||||
local function frames_reset()
|
||||
frames = { n = 0, i = 0, dt = {}, lua = {}, builds = {} }
|
||||
end
|
||||
frames_reset()
|
||||
|
||||
function M.clock()
|
||||
if html5 then
|
||||
return (tonumber(html5.run("performance.now()")) or 0) / 1000
|
||||
end
|
||||
return socket.gettime()
|
||||
end
|
||||
|
||||
function M.frame(dt, lua_secs, build_secs)
|
||||
frames.i = frames.i % FRAMES + 1
|
||||
frames.n = math.min(frames.n + 1, FRAMES)
|
||||
frames.dt[frames.i] = dt
|
||||
frames.lua[frames.i] = lua_secs
|
||||
if build_secs and #frames.builds < FRAMES then
|
||||
frames.builds[#frames.builds + 1] = build_secs
|
||||
end
|
||||
end
|
||||
|
||||
local function stats(list, n)
|
||||
local t, sum = {}, 0
|
||||
for i = 1, n do
|
||||
t[i] = list[i] * 1000
|
||||
sum = sum + t[i]
|
||||
end
|
||||
table.sort(t)
|
||||
local function r(v)
|
||||
return math.floor(v * 100 + 0.5) / 100
|
||||
end
|
||||
local function q(p)
|
||||
return n > 0 and r(t[math.min(n, math.floor(p * n) + 1)]) or 0
|
||||
end
|
||||
return { n = n, mean = n > 0 and r(sum / n) or 0, p50 = q(0.5), p95 = q(0.95), max = q(1) }
|
||||
end
|
||||
|
||||
function COMMANDS.frames(g, cmd)
|
||||
local n = frames.n
|
||||
local total = 0
|
||||
for i = 1, n do
|
||||
total = total + frames.dt[i]
|
||||
end
|
||||
local res = { ok = true, fps = total > 0 and math.floor(n / total * 10 + 0.5) / 10 or 0,
|
||||
dt = stats(frames.dt, n), lua = stats(frames.lua, n), build = stats(frames.builds, #frames.builds) }
|
||||
if cmd.args[1] == "reset" then
|
||||
frames_reset()
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
function COMMANDS.mark(g, cmd)
|
||||
print("mm: shot " .. tostring(cmd.args[1]))
|
||||
return { ok = true }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#version 140
|
||||
|
||||
// The viewport node: shows the moon scene the render script drew into `moon_tex` (HDR,
|
||||
// 2x the node's size: linear filtering at the pixel centers averages 2x2 samples) plus
|
||||
// up to 2x the node's size: linear filtering at the pixel centers averages 2x2 samples) plus
|
||||
// its bloom (`bloom_tex`, half size, upsampled), with three.js's output pass: ACES
|
||||
// filmic tone mapping (exposure) and the sRGB transfer.
|
||||
// Without a moon (moon_rect.z == 0) the node keeps its plain color.
|
||||
|
||||
+10
-5
@@ -24,10 +24,13 @@ local FOV = math.rad(35)
|
||||
local RESUME = 1.5 -- seconds before auto-rotation resumes after a drag
|
||||
local ROTATE_SPEED = 0.6
|
||||
local DAMPING = 0.05
|
||||
local BUDGET = html5 and 0.012 or 0.006 -- seconds of generation work per frame (the web runs
|
||||
-- plain Lua, no LuaJIT: several times slower, so it gets more of each frame)
|
||||
local BUDGET = html5 and 0.008 or 0.006 -- seconds of generation work per frame (the web runs
|
||||
-- plain Lua, no LuaJIT: several times slower, so it gets more of each frame; 12 ms there
|
||||
-- overran 60 fps frames: 33 fps for the same ~4 s)
|
||||
local CACHE_SIZE = 6
|
||||
local SUPERSAMPLE = 2 -- the scene renders at 2x and is averaged down (antialiasing)
|
||||
local SUPERSAMPLE = 2 -- the scene renders at 2x and is averaged down (antialiasing),
|
||||
-- counted in GUI reference pixels: a HiDPI display (or UI zoom) already gives the viewport
|
||||
-- more window pixels, so the target is 2x the reference size, at least the window size
|
||||
local CHARGE = 1.1 -- reveal: charge-up before the burst (s)
|
||||
local BURST = 0.9 -- reveal: the burst itself (s)
|
||||
local TIMEOUT = 4 -- reveal: burst without the mesh after CHARGE + this (s)
|
||||
@@ -199,7 +202,9 @@ local function start_job(self, id, key, params)
|
||||
end
|
||||
local m = terrain.generate(params, checkpoint)
|
||||
job.gen_work = job.work + (now() - job.resume_at)
|
||||
checkpoint()
|
||||
local strip = terrain.strip(m.n)
|
||||
checkpoint()
|
||||
local buf = buffer.create(#strip, STREAMS)
|
||||
local pos = buffer.get_stream(buf, hash("position"))
|
||||
local nor = buffer.get_stream(buf, hash("normal"))
|
||||
@@ -528,13 +533,13 @@ function update(self, dt)
|
||||
self.spin = self.spin + self.spin_speed * dt
|
||||
go.set_rotation(TILT * vmath.quat_rotation_y(self.spin), "surface")
|
||||
|
||||
local ss = SUPERSAMPLE
|
||||
local ss = math.max(1, SUPERSAMPLE / math.max(1, moon.unit))
|
||||
local aspect = rect.w / rect.h
|
||||
local eye = update_camera(self, aspect)
|
||||
f.visible = values ~= nil
|
||||
f.has_mesh = self.shown_key ~= nil and show_moon
|
||||
f.rect = rect
|
||||
f.rt_w, f.rt_h = rect.w * ss, rect.h * ss
|
||||
f.rt_w, f.rt_h = math.floor(rect.w * ss + 0.5), math.floor(rect.h * ss + 0.5)
|
||||
f.unit = moon.unit * ss
|
||||
f.view = vmath.matrix4_look_at(eye, vmath.vector3(0, 0, 0), vmath.vector3(0, 1, 0))
|
||||
f.proj = vmath.matrix4_perspective(self.fov, aspect, 0.1, 500)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-- Moon Miners render script: Defold's default render script (builtins
|
||||
-- default.render_script of the pinned engine) plus the moon pass. Before the GUI, the moon
|
||||
-- scene (moon/moon.collection, see modules/moon/moon.lua) is drawn into an HDR render
|
||||
-- target at 2x the viewport size: backdrop, stars, the moon, the atmosphere shell; then
|
||||
-- target at 2x the viewport's GUI reference size (at least its window size; moon.script): backdrop, stars, the moon, the atmosphere shell; then
|
||||
-- its bloom (three.js UnrealBloomPass) into smaller targets. The GUI then draws it in its viewport node (material moon/materials/moon_gui, sampler
|
||||
-- `moon_tex`), tone mapped, so HUD panels, captions and popups layer over it as usual.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-- the focus ring) scroll the scroll area under the mouse
|
||||
-- (else the one holding the focused element, else the biggest one), like a browser.
|
||||
-- F11 toggles fullscreen; Ctrl + / Ctrl - / Ctrl 0 change the UI scale (modules/screen),
|
||||
-- like the browser's zoom.
|
||||
-- like the browser's zoom. F3 (debug builds) shows the engine's profiler (frame times).
|
||||
--
|
||||
-- Text fields made in a build register in g.inputs ({ input = Druid input, submit = fn,
|
||||
-- step = { min, max } }: modules/field, the refinery's quantity); core/main resets the list
|
||||
@@ -30,6 +30,7 @@ local SHIFT = { [hash("key_lshift")] = true, [hash("key_rshift")] = true }
|
||||
local ALT = { [hash("key_lalt")] = true, [hash("key_ralt")] = true }
|
||||
local CTRL = { [hash("key_lctrl")] = true, [hash("key_rctrl")] = true }
|
||||
local F11 = hash("key_f11")
|
||||
local F3 = hash("key_f3")
|
||||
local ESC = hash("key_esc")
|
||||
local ZOOM = { [hash("key_plus")] = 1, [hash("key_equals")] = 1, [hash("key_numpad_add")] = 1,
|
||||
[hash("key_minus")] = -1, [hash("key_numpad_subtract")] = -1, [hash("key_0")] = 0, [hash("key_numpad_0")] = 0 }
|
||||
@@ -172,6 +173,11 @@ function M.before(g, action_id, action, input)
|
||||
screen.toggle_fullscreen()
|
||||
return true
|
||||
end
|
||||
if action_id == F3 and action.pressed and profiler and sys.get_engine_info().is_debug then
|
||||
g.profiler_ui = not g.profiler_ui
|
||||
profiler.enable_ui(g.profiler_ui)
|
||||
return true
|
||||
end
|
||||
if g.mods.ctrl and action_id and ZOOM[action_id] and action.pressed then
|
||||
screen.step_ui_scale(ZOOM[action_id])
|
||||
return true
|
||||
|
||||
@@ -465,9 +465,12 @@ local TEST_API = {
|
||||
}
|
||||
|
||||
function M.update(g, dt)
|
||||
local t0 = test.enabled and test.clock()
|
||||
local built
|
||||
local W, H, scale = screen.layout()
|
||||
if g.dirty or W ~= g.size[1] or H ~= g.size[2] or scale ~= g.size[3] then
|
||||
M.build(g)
|
||||
built = t0 and test.clock() - t0
|
||||
end
|
||||
if g.boot_tick and not g.state then
|
||||
local left = math.max(0, math.ceil(g.boot_tick.at - socket.gettime()))
|
||||
@@ -505,6 +508,9 @@ function M.update(g, dt)
|
||||
g.autoplay:update(dt)
|
||||
end
|
||||
test.update(g, function(id, a) return input(g, id, a) end, TEST_API)
|
||||
if t0 then
|
||||
test.frame(dt, test.clock() - t0, built)
|
||||
end
|
||||
end
|
||||
|
||||
function M.on_message(g, message_id, message, sender)
|
||||
|
||||
Reference in new issue
Block a user