Emscripten compatibility (#161)
* Major refactoring of the main loop(s) and control flow (WIP) run_at_fps() is gone 🦀 Instead of nested blocking event loops, there is now an eventloop API that manages an explicit stack of scenes. This makes Taisei a lot more portable to async environments where spinning a loop forever without yielding control simply is not an option, and that is the entire point of this change. A prime example of such an environment is the Web (via emscripten). Taisei was able to run there through a terrible hack: inserting emscripten_sleep calls into the loop, which would yield to the browser. This has several major drawbacks: first of all, every function that could possibly call emscripten_sleep must be compiled into a special kind of bytecode, which then has to be interpreted at runtime, *much* slower than JITed WebAssembly. And that includes *everything* down the call stack, too! For more information, see https://emscripten.org/docs/porting/emterpreter.html Even though that method worked well enough for experimenting, despite suboptimal performance, there is another obvious drawback: emscripten_sleep is implemented via setTimeout(), which can be very imprecise and is generally not reliable for fluid animation. Browsers actually have an API specifically for that use case: window.requestAnimationFrame(), but Taisei's original blocking control flow style is simply not compatible with it. Emscripten exposes this API with its emscripten_set_main_loop(), which the eventloop backend now uses on that platform. Unfortunately, C is still C, with no fancy closures or coroutines. With blocking calls into menu/scene loops gone, the control flow is reimplemented via so-called (pun intended) "call chains". That is basically an euphemism for callback hell. With manual memory management and zero type-safety. Not that the menu system wasn't shitty enough already. I'll just keep telling myself that this is all temporary and will be replaced with scripts in v1.4. * improve build system for emscripten + various fixes * squish menu bugs * improve emscripten event loop; disable EMULATE_FUNCTION_POINTER_CASTS Note that stock freetype does not work without EMULATE_FUNCTION_POINTER_CASTS; use a patched version from the "emscripten" branch here: https://github.com/taisei-project/freetype2/tree/emscripten * Enable -Wcast-function-type Calling functions through incompatible pointers is nasal demons and doesn't work in WASM. * webgl: workaround a crash on some browsers * emscripten improvements: * Persist state (config, progress, replays, ...) in local IndexDB * Simpler HTML shell (temporary) * Enable more optimizations * fix build if validate_glsl=false * emscripten: improve asset packaging, with local cache Note that even though there are rules to build audio bundles, audio does *not* work yet. It looks like SDL2_mixer can not work without threads, which is a problem. Yet another reason to write an OpenAL backend - emscripten supports that natively. * emscripten: customize the html shell * emscripten: force "show log" checkbox unchecked initially * emscripten: remove quit shortcut from main menu (since there's no quit) * emscripten: log area fixes * emscripten/webgl: workaround for fullscreen viewport issue * emscripten: implement frameskip * emscripter: improve framerate limiter * align List to at least 8 bytes (shut up warnings) * fix non-emscripten builds * improve fullscreen handling, mainly for emscripten * Workaround to make audio work in chromium emscripten-core/emscripten#6511 * emscripten: better vsync handling; enable vsync & disable fxaa by default
This commit is contained in:
parent
c8e281bc02
commit
180f9e3856
94 changed files with 2505 additions and 830 deletions
|
@ -34,7 +34,7 @@ Build-only dependencies
|
|||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- Python >= 3.5
|
||||
- meson >= 0.45.0 (build system; >=0.48.0 recommended)
|
||||
- meson >= 0.46.0 (build system; >=0.48.0 recommended)
|
||||
|
||||
Optional:
|
||||
|
||||
|
|
BIN
emscripten/background.webp
Normal file
BIN
emscripten/background.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 287 KiB |
BIN
emscripten/favicon.ico
Normal file
BIN
emscripten/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
14
emscripten/meson.build
Normal file
14
emscripten/meson.build
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
em_preamble = files('preamble.js')
|
||||
em_shell = files('shell.html')
|
||||
|
||||
if host_machine.system() == 'emscripten'
|
||||
install_data(
|
||||
files(
|
||||
'background.webp',
|
||||
'scythe.webp',
|
||||
'favicon.ico',
|
||||
),
|
||||
install_dir : bindir
|
||||
)
|
||||
endif
|
20
emscripten/preamble.js
Normal file
20
emscripten/preamble.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
|
||||
Module['preRun'].push(function() {
|
||||
ENV["TAISEI_NOASYNC"] = "1";
|
||||
ENV["TAISEI_NOUNLOAD"] = "1";
|
||||
ENV["TAISEI_PREFER_SDL_VIDEODRIVERS"] = "emscripten";
|
||||
ENV["TAISEI_RENDERER"] = "gles30";
|
||||
|
||||
FS.mkdir('/persistent');
|
||||
FS.mount(IDBFS, {}, '/persistent');
|
||||
});
|
||||
|
||||
function SyncFS(is_load, ccptr) {
|
||||
FS.syncfs(is_load, function(err) {
|
||||
Module['ccall'](
|
||||
'vfs_sync_callback',
|
||||
null, ["boolean", "string", "number"],
|
||||
[is_load, err, ccptr]
|
||||
);
|
||||
});
|
||||
};
|
BIN
emscripten/scythe.webp
Normal file
BIN
emscripten/scythe.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
269
emscripten/shell.html
Normal file
269
emscripten/shell.html
Normal file
|
@ -0,0 +1,269 @@
|
|||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico"/>
|
||||
<title>Taisei Project — Web version (Experimental!)</title>
|
||||
<style>
|
||||
body {
|
||||
background-image: url('background.webp');
|
||||
background-color: #111111;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: cover;
|
||||
color: #eeeeee;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #000000;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.emscripten {
|
||||
padding-right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
textarea.emscripten {
|
||||
font-family: monospace;
|
||||
width: 100%;
|
||||
resize: none;
|
||||
background: none;
|
||||
border: 0px none;
|
||||
color: #eeeeee;
|
||||
overflow: overlay;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
div.emscripten {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.emscripten_border {
|
||||
border: 0px none;
|
||||
}
|
||||
|
||||
div.centered {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: translateY(50%);
|
||||
background-color: #000000C0;
|
||||
padding: 16px 0px;
|
||||
}
|
||||
|
||||
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
|
||||
canvas.emscripten {
|
||||
border: 0px none;
|
||||
background-color: none;
|
||||
}
|
||||
|
||||
#spinner {
|
||||
overflow: visible;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
#progress {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
#logContainer {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#logToggleContainer {
|
||||
display: /* inline-block */ none;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
progress {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
border: 0px none;
|
||||
background-color: #eee;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background-color: #eee;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
background-image:
|
||||
-webkit-linear-gradient(top, rgba(0, 0, 0, 0.25), rgba(255, 255, 255, 0.25)),
|
||||
-webkit-linear-gradient(left, #5c7da8, #6db0ed);
|
||||
|
||||
border-radius: 5px;
|
||||
background-size: 35px 20px, 100% 100%, 100% 100%;
|
||||
}
|
||||
|
||||
progress::-moz-progress-bar {
|
||||
background-image:
|
||||
-moz-linear-gradient(top, rgba(0, 0, 0, 0.25), rgba(255, 255, 255, 0.25)),
|
||||
-moz-linear-gradient(left, #5c7da8, #6db0ed);
|
||||
|
||||
border-radius: 5px;
|
||||
background-size: 35px 20px, 100% 100%, 100% 100%;
|
||||
}
|
||||
|
||||
div.spinner {
|
||||
height: 75px;
|
||||
width: 75px;
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
img.spinner {
|
||||
height: 75px;
|
||||
width: 75px;
|
||||
margin: 4px auto;
|
||||
-webkit-animation: rotation 0.8s linear infinite;
|
||||
-moz-animation: rotation 0.8s linear infinite;
|
||||
-o-animation: rotation 0.8s linear infinite;
|
||||
animation: rotation 0.6s linear infinite;
|
||||
border: 0px none;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotation {
|
||||
from { -webkit-transform: rotate(0deg); }
|
||||
to { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@-moz-keyframes rotation {
|
||||
from { -moz-transform: rotate(0deg); }
|
||||
to { -moz-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@-o-keyframes rotation {
|
||||
from { -o-transform: rotate(0deg); }
|
||||
to { -o-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="centered">
|
||||
<div class="spinner" id="spinner"><img src="scythe.webp" class="spinner"/></div>
|
||||
<div class="emscripten" id="status">Girls are now downloading, please wait warmly…</div>
|
||||
<div class="emscripten">
|
||||
<progress value="0" max="100" id="progress" ></progress>
|
||||
</div>
|
||||
<div class="emscripten_border" id="canvasContainer" hidden>
|
||||
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
|
||||
</div>
|
||||
<div id="logToggleContainer" hidden>
|
||||
<input type="checkbox" name="logToggle" id="logToggle" onclick="toggleLog()"/>
|
||||
<label for="logToggle"> Show log</label>
|
||||
</div>
|
||||
<div id="logContainer" hidden>
|
||||
<textarea class="emscripten" id="output" rows="12" readonly></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var statusElement = document.getElementById('status');
|
||||
var progressElement = document.getElementById('progress');
|
||||
var spinnerElement = document.getElementById('spinner');
|
||||
var canvasContainerElement = document.getElementById('canvasContainer');
|
||||
var logToggleElement = document.getElementById('logToggle');
|
||||
var logContainerElement = document.getElementById('logContainer');
|
||||
var logOutputElement = document.getElementById('output');
|
||||
var dlMessage = statusElement.innerText;
|
||||
logToggleElement.checked = false;
|
||||
|
||||
function toggleLog() {
|
||||
logContainerElement.hidden = !logToggleElement.checked;
|
||||
logOutputElement.scrollTop = logOutputElement.scrollHeight;
|
||||
}
|
||||
|
||||
var Taisei = {
|
||||
preRun: [],
|
||||
postRun: [],
|
||||
onFirstFrame: function() {
|
||||
canvasContainerElement.hidden = false;
|
||||
logToggleContainer.style.display = "inline-block";
|
||||
Taisei.setStatus('', true);
|
||||
},
|
||||
print: (function() {
|
||||
logOutputElement.value = ''; // clear browser cache
|
||||
return function(text) {
|
||||
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
|
||||
console.log(text);
|
||||
logOutputElement.value += text + "\n";
|
||||
logOutputElement.scrollTop = logOutputElement.scrollHeight; // focus on bottom
|
||||
};
|
||||
})(),
|
||||
printErr: function(text) {
|
||||
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
|
||||
console.error(text);
|
||||
},
|
||||
canvas: (function() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
|
||||
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
|
||||
// application robust, you may want to override this behavior before shipping!
|
||||
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
|
||||
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
|
||||
|
||||
return canvas;
|
||||
})(),
|
||||
setStatus: function(text, force) {
|
||||
if (!text && !force) return;
|
||||
if (!Taisei.setStatus.last) Taisei.setStatus.last = { time: Date.now(), text: '' };
|
||||
if (text === Taisei.setStatus.last.text) return;
|
||||
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
|
||||
var now = Date.now();
|
||||
if (m && now - Taisei.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
|
||||
Taisei.setStatus.last.time = now;
|
||||
Taisei.setStatus.last.text = text;
|
||||
if (m) {
|
||||
text = m[1];
|
||||
progressElement.value = parseInt(m[2])*100;
|
||||
progressElement.max = parseInt(m[4])*100;
|
||||
progressElement.hidden = false;
|
||||
spinnerElement.hidden = false;
|
||||
} else {
|
||||
progressElement.value = null;
|
||||
progressElement.max = null;
|
||||
progressElement.hidden = true;
|
||||
if (!text) spinnerElement.hidden = true;
|
||||
}
|
||||
statusElement.innerText = text.replace(/^Downloading(?: data)?\.\.\./, dlMessage).replace('...', '…');
|
||||
console.log("[STATUS] " + statusElement.innerText);
|
||||
},
|
||||
totalDependencies: 0,
|
||||
monitorRunDependencies: function(left) {
|
||||
this.totalDependencies = Math.max(this.totalDependencies, left);
|
||||
Taisei.setStatus(left ? 'Preparing… (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
|
||||
}
|
||||
};
|
||||
|
||||
window.onerror = function(error) {
|
||||
Taisei.setStatus('Error: ' + error);
|
||||
};
|
||||
</script>
|
||||
{{{ SCRIPT }}}
|
||||
</body>
|
||||
</html>
|
14
external/meson.build
vendored
14
external/meson.build
vendored
|
@ -1,5 +1,11 @@
|
|||
|
||||
install_data(
|
||||
join_paths('gamecontrollerdb', 'gamecontrollerdb.txt'),
|
||||
install_dir : data_path
|
||||
)
|
||||
gamecontrollerdb_name = 'gamecontrollerdb.txt'
|
||||
gamecontrollerdb_relpath = join_paths('gamecontrollerdb', gamecontrollerdb_name)
|
||||
gamecontrollerdb_path = join_paths(meson.current_source_dir(), gamecontrollerdb_relpath)
|
||||
|
||||
if host_machine.system() != 'emscripten'
|
||||
install_data(
|
||||
gamecontrollerdb_relpath,
|
||||
install_dir : data_path
|
||||
)
|
||||
endif
|
||||
|
|
24
meson.build
24
meson.build
|
@ -1,7 +1,7 @@
|
|||
project('taisei', 'c',
|
||||
license : 'MIT',
|
||||
version : 'v1.3-dev',
|
||||
meson_version : '>=0.45.0',
|
||||
meson_version : '>=0.46.0',
|
||||
default_options : [
|
||||
'c_std=c11',
|
||||
|
||||
|
@ -34,6 +34,7 @@ taisei_c_args = [
|
|||
'-Wabsolute-value',
|
||||
'-Wcast-align',
|
||||
'-Wcast-align=strict',
|
||||
'-Wcast-function-type',
|
||||
'-Wclobbered',
|
||||
'-Wduplicated-branches',
|
||||
'-Wduplicated-cond',
|
||||
|
@ -90,7 +91,7 @@ if sm_check.stderr() != ''
|
|||
warning('Submodule check completed with errors:\n@0@'.format(sm_check.stderr()))
|
||||
endif
|
||||
|
||||
static = get_option('static')
|
||||
static = get_option('static') or (host_machine.system() == 'emscripten')
|
||||
|
||||
dep_freetype = dependency('freetype2', required : true, static : static)
|
||||
dep_png = dependency('libpng', version : '>=1.5', required : true, static : static)
|
||||
|
@ -130,9 +131,14 @@ if host_machine.system() == 'windows'
|
|||
taisei_deps += cc.find_library('shlwapi')
|
||||
endif
|
||||
|
||||
package_data = get_option('package_data')
|
||||
enable_zip = get_option('enable_zip')
|
||||
package_data = (package_data == 'auto' ? enable_zip : package_data == 'true')
|
||||
if host_machine.system() == 'emscripten'
|
||||
package_data = false
|
||||
enable_zip = false
|
||||
else
|
||||
package_data = get_option('package_data')
|
||||
enable_zip = get_option('enable_zip')
|
||||
package_data = (package_data == 'auto' ? enable_zip : package_data == 'true')
|
||||
endif
|
||||
|
||||
if enable_zip
|
||||
if not dep_zip.found()
|
||||
|
@ -163,6 +169,9 @@ config.set('TAISEI_BUILDCONF_HAVE_MAX_ALIGN_T', cc.compiles('#include <stddef.h>
|
|||
|
||||
prefer_relpath_systems = [
|
||||
'windows',
|
||||
]
|
||||
|
||||
force_relpath_systems = [
|
||||
'emscripten',
|
||||
]
|
||||
|
||||
|
@ -183,7 +192,9 @@ if macos_app_bundle
|
|||
else
|
||||
datadir = get_option('datadir')
|
||||
|
||||
if get_option('install_relative') == 'auto'
|
||||
if force_relpath_systems.contains(host_machine.system())
|
||||
config.set('TAISEI_BUILDCONF_RELATIVE_DATA_PATH', true)
|
||||
elif get_option('install_relative') == 'auto'
|
||||
config.set('TAISEI_BUILDCONF_RELATIVE_DATA_PATH', prefer_relpath_systems.contains(host_machine.system()))
|
||||
else
|
||||
config.set('TAISEI_BUILDCONF_RELATIVE_DATA_PATH', get_option('install_relative') == 'true')
|
||||
|
@ -275,6 +286,7 @@ endif
|
|||
version_deps = []
|
||||
|
||||
subdir('misc')
|
||||
subdir('emscripten')
|
||||
subdir('external')
|
||||
subdir('resources')
|
||||
subdir('doc')
|
||||
|
|
|
@ -37,7 +37,7 @@ if validate_glsl
|
|||
fname = '@0@'.format(src)
|
||||
stage = fname.split('.')[-2]
|
||||
|
||||
glsl_targets += custom_target(fname.underscorify(),
|
||||
spirv = custom_target('SPIRV_' + fname.underscorify(),
|
||||
input : src,
|
||||
output : '@BASENAME@.spv',
|
||||
command : [
|
||||
|
@ -48,6 +48,26 @@ if validate_glsl
|
|||
build_by_default : true,
|
||||
depfile : '@0@.d'.format(fname.underscorify()),
|
||||
)
|
||||
|
||||
spirv_targets += spirv
|
||||
|
||||
if transpile_glsl
|
||||
essl = custom_target('ESSL_' + fname.underscorify(),
|
||||
input : spirv,
|
||||
output : '@BASENAME@.glsl',
|
||||
command : [
|
||||
spvc_command,
|
||||
'--output', '@OUTPUT@', '@INPUT@',
|
||||
spvc_args,
|
||||
get_variable('spvc_@0@_args'.format(stage)),
|
||||
],
|
||||
install : false,
|
||||
build_by_default : true,
|
||||
depfile : '@0@.d'.format(fname.underscorify()),
|
||||
)
|
||||
|
||||
essl_targets += essl
|
||||
endif
|
||||
endforeach
|
||||
endif
|
||||
# @end validate
|
||||
|
|
|
@ -91,9 +91,8 @@ subdirs = [
|
|||
# @end subdirs
|
||||
]
|
||||
|
||||
glsl_targets = []
|
||||
|
||||
validate_glsl = false
|
||||
spirv_targets = []
|
||||
essl_targets = []
|
||||
|
||||
glslc_args = [
|
||||
# '-fauto-bind-uniforms',
|
||||
|
@ -124,9 +123,31 @@ glslc_vert_args = [
|
|||
'-DVERT_STAGE',
|
||||
]
|
||||
|
||||
if get_option('validate_glsl') != 'false'
|
||||
spvc_args = [
|
||||
'--version', '300',
|
||||
'--es',
|
||||
'--remove-unused-variables',
|
||||
]
|
||||
|
||||
spvc_frag_args = [
|
||||
'--stage', 'frag',
|
||||
]
|
||||
|
||||
spvc_vert_args = [
|
||||
'--stage', 'vert',
|
||||
]
|
||||
|
||||
if host_machine.system() == 'emscripten'
|
||||
validate_glsl = 'true'
|
||||
transpile_glsl = true
|
||||
else
|
||||
validate_glsl = get_option('validate_glsl')
|
||||
transpile_glsl = false
|
||||
endif
|
||||
|
||||
if validate_glsl != 'false'
|
||||
glslc_command = find_program('glslc',
|
||||
required : (get_option('validate_glsl') == 'true')
|
||||
required : (validate_glsl == 'true')
|
||||
)
|
||||
|
||||
if glslc_command.found()
|
||||
|
@ -143,13 +164,23 @@ if get_option('validate_glsl') != 'false'
|
|||
else
|
||||
warning(test_result.stderr())
|
||||
|
||||
if get_option('validate_glsl') == 'auto'
|
||||
if validate_glsl == 'auto'
|
||||
warning('glslc test failed, you probably have an incompatible version. GLSL validation will be disabled.')
|
||||
validate_glsl = false
|
||||
else
|
||||
error('glslc test failed, you probably have an incompatible version.')
|
||||
endif
|
||||
endif
|
||||
else
|
||||
validate_glsl = false
|
||||
endif
|
||||
|
||||
if validate_glsl and transpile_glsl
|
||||
spvc_command = find_program('spirv-cross', required : true)
|
||||
glslc_args += ['-Os', '-g']
|
||||
endif
|
||||
else
|
||||
validate_glsl = false
|
||||
endif
|
||||
|
||||
# @begin validate
|
||||
|
@ -158,7 +189,7 @@ if validate_glsl
|
|||
fname = '@0@'.format(src)
|
||||
stage = fname.split('.')[-2]
|
||||
|
||||
glsl_targets += custom_target(fname.underscorify(),
|
||||
spirv = custom_target('SPIRV_' + fname.underscorify(),
|
||||
input : src,
|
||||
output : '@BASENAME@.spv',
|
||||
command : [
|
||||
|
@ -169,6 +200,26 @@ if validate_glsl
|
|||
build_by_default : true,
|
||||
depfile : '@0@.d'.format(fname.underscorify()),
|
||||
)
|
||||
|
||||
spirv_targets += spirv
|
||||
|
||||
if transpile_glsl
|
||||
essl = custom_target('ESSL_' + fname.underscorify(),
|
||||
input : spirv,
|
||||
output : '@BASENAME@.glsl',
|
||||
command : [
|
||||
spvc_command,
|
||||
'--output', '@OUTPUT@', '@INPUT@',
|
||||
spvc_args,
|
||||
get_variable('spvc_@0@_args'.format(stage)),
|
||||
],
|
||||
install : false,
|
||||
build_by_default : true,
|
||||
depfile : '@0@.d'.format(fname.underscorify()),
|
||||
)
|
||||
|
||||
essl_targets += essl
|
||||
endif
|
||||
endforeach
|
||||
endif
|
||||
# @end validate
|
||||
|
@ -176,3 +227,5 @@ endif
|
|||
foreach sd : subdirs
|
||||
subdir(sd)
|
||||
endforeach
|
||||
|
||||
shaders_build_dir = meson.current_build_dir()
|
||||
|
|
|
@ -5,6 +5,50 @@ packages = [
|
|||
'00-taisei',
|
||||
]
|
||||
|
||||
if host_machine.system() == 'emscripten'
|
||||
em_data_prefix = '/@0@'.format(config.get_unquoted('TAISEI_BUILDCONF_DATA_PATH'))
|
||||
em_bundles = ['gfx', 'misc']
|
||||
|
||||
if get_option('a_default') != 'null'
|
||||
em_bundles += ['bgm', 'sfx']
|
||||
endif
|
||||
|
||||
em_bundle_gfx_patterns = [
|
||||
'gfx/*.png',
|
||||
'gfx/*.webp',
|
||||
'fonts/*.ttf',
|
||||
'fonts/*.otf',
|
||||
]
|
||||
|
||||
em_bundle_misc_patterns = [
|
||||
'gfx/*.ani',
|
||||
'gfx/*.spr',
|
||||
'gfx/*.tex',
|
||||
'fonts/*.font',
|
||||
'models/*.obj',
|
||||
|
||||
# We don't want to include the shader sources here, we're going to translate them first.
|
||||
'shader/*.prog'
|
||||
]
|
||||
|
||||
em_bundle_bgm_patterns = [
|
||||
'bgm/*',
|
||||
]
|
||||
|
||||
em_bundle_sfx_patterns = [
|
||||
'sfx/*',
|
||||
]
|
||||
|
||||
foreach bundle : em_bundles
|
||||
set_variable('em_bundle_@0@_deps'.format(bundle), [])
|
||||
set_variable('em_bundle_@0@_files'.format(bundle), [])
|
||||
set_variable('em_bundle_@0@_packer_args'.format(bundle), [])
|
||||
endforeach
|
||||
|
||||
# These are all text files that compress well
|
||||
em_bundle_misc_packer_args += ['--lz4']
|
||||
endif
|
||||
|
||||
foreach pkg : packages
|
||||
pkg_pkgdir = '@0@.pkgdir'.format(pkg)
|
||||
pkg_zip = '@0@.zip'.format(pkg)
|
||||
|
@ -12,7 +56,26 @@ foreach pkg : packages
|
|||
|
||||
subdir(pkg_pkgdir)
|
||||
|
||||
if package_data
|
||||
if host_machine.system() == 'emscripten'
|
||||
foreach bundle : em_bundles
|
||||
var_patterns = 'em_bundle_@0@_patterns'.format(bundle)
|
||||
var_files = 'em_bundle_@0@_files'.format(bundle)
|
||||
var_packer_args = 'em_bundle_@0@_packer_args'.format(bundle)
|
||||
|
||||
glob_result = run_command(glob_command, pkg_path, get_variable(var_patterns))
|
||||
assert(glob_result.returncode() == 0, 'Glob script failed')
|
||||
|
||||
foreach file : glob_result.stdout().strip().split('\n')
|
||||
fpath = join_paths(meson.current_source_dir(), pkg_pkgdir, file)
|
||||
|
||||
set_variable(var_files, get_variable(var_files) + [fpath])
|
||||
|
||||
set_variable(var_packer_args, get_variable(var_packer_args) + [
|
||||
'--preload', '@0@@@1@/@2@'.format(fpath, em_data_prefix, file)
|
||||
])
|
||||
endforeach
|
||||
endforeach
|
||||
elif package_data
|
||||
custom_target(pkg_zip,
|
||||
command : [pack_command,
|
||||
pkg_path,
|
||||
|
@ -30,3 +93,69 @@ foreach pkg : packages
|
|||
install_subdir(pkg_pkgdir, install_dir : data_path, exclude_files : glob_result.stdout().split('\n'))
|
||||
endif
|
||||
endforeach
|
||||
|
||||
if host_machine.system() == 'emscripten'
|
||||
# First add some stuff that isn't sourced from resources/
|
||||
|
||||
em_bundle_misc_files += gamecontrollerdb_path
|
||||
em_bundle_misc_packer_args += [
|
||||
'--preload', '@0@@@1@/@2@'.format(gamecontrollerdb_path, em_data_prefix, gamecontrollerdb_name)
|
||||
]
|
||||
|
||||
foreach shader : essl_targets
|
||||
# This one is especially dirty!
|
||||
abs_path = shader.full_path()
|
||||
assert(abs_path.startswith(shaders_build_dir), 'Assumption about shader output location violated')
|
||||
rel_path = '@0@/shader@1@'.format(em_data_prefix, abs_path.split(shaders_build_dir)[1])
|
||||
|
||||
em_bundle_misc_deps += shader
|
||||
em_bundle_misc_packer_args += [
|
||||
'--preload', '@0@@@1@'.format(abs_path, rel_path)
|
||||
]
|
||||
endforeach
|
||||
|
||||
packer = find_program('file_packager') # should be provided by cross file
|
||||
em_bundle_link_args = [] # We'll pass these to the final emcc linking step
|
||||
|
||||
# Finally, set up build targets for our bundles
|
||||
|
||||
foreach bundle : em_bundles
|
||||
var_files = 'em_bundle_@0@_files'.format(bundle)
|
||||
var_deps = 'em_bundle_@0@_deps'.format(bundle)
|
||||
var_packer_args = 'em_bundle_@0@_packer_args'.format(bundle)
|
||||
|
||||
data_name = 'bundle_@0@.data'.format(bundle)
|
||||
loader_name = 'bundle_@0@.js'.format(bundle)
|
||||
out_loader = join_paths(meson.current_build_dir(), '@0@.raw'.format(loader_name))
|
||||
|
||||
bundle_data = custom_target(data_name,
|
||||
command : [
|
||||
packer,
|
||||
'@OUTPUT0@',
|
||||
'--js-output=@0@'.format(out_loader), # No, this one does not accept "--js-output foobar.js"
|
||||
'--use-preload-cache',
|
||||
'--no-heap-copy',
|
||||
'--from-emcc',
|
||||
get_variable(var_packer_args)
|
||||
],
|
||||
output : data_name,
|
||||
depends : get_variable(var_deps),
|
||||
depend_files : get_variable(var_files),
|
||||
install : true,
|
||||
install_dir : bindir,
|
||||
)
|
||||
|
||||
bundle_loader = custom_target(loader_name,
|
||||
command : [
|
||||
em_set_bundle_uuid_command,
|
||||
out_loader,
|
||||
'--sha1', bundle_data,
|
||||
'--output', '@OUTPUT@',
|
||||
],
|
||||
output : loader_name,
|
||||
install : false,
|
||||
)
|
||||
|
||||
em_bundle_link_args += ['--pre-js', bundle_loader]
|
||||
endforeach
|
||||
endif
|
||||
|
|
66
scripts/em-set-bundle-uuid.py
Executable file
66
scripts/em-set-bundle-uuid.py
Executable file
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from taiseilib.common import (
|
||||
run_main,
|
||||
update_text_file,
|
||||
)
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
meta_re = re.compile(r'(.*loadPackage\()({.*?})(\);.*)', re.DOTALL)
|
||||
|
||||
|
||||
def main(args):
|
||||
parser = argparse.ArgumentParser(description='Change package UUID in JavaScript loader generated by Emscripten\'s file_packager.py', prog=args[0])
|
||||
|
||||
parser.add_argument('loader',
|
||||
help='the .js loader file',
|
||||
metavar='FILE',
|
||||
type=Path,
|
||||
)
|
||||
|
||||
parser.add_argument('--output', '-o',
|
||||
help='write result to FILE (default: overwrite input)',
|
||||
metavar='FILE',
|
||||
type=Path,
|
||||
)
|
||||
|
||||
g = parser.add_mutually_exclusive_group(required=True)
|
||||
|
||||
g.add_argument('--uuid',
|
||||
help='manually specify an UUID',
|
||||
metavar='UUID',
|
||||
type=str,
|
||||
)
|
||||
|
||||
g.add_argument('--sha1',
|
||||
help='take SHA1 of FILE and use that as an UUID',
|
||||
metavar='FILE',
|
||||
type=Path,
|
||||
)
|
||||
|
||||
args = parser.parse_args(args[1:])
|
||||
|
||||
if args.uuid is None:
|
||||
args.uuid = hashlib.sha1(args.sha1.read_bytes()).hexdigest()
|
||||
|
||||
if args.output is None:
|
||||
args.output = args.loader
|
||||
|
||||
pre, meta, post = meta_re.match(args.loader.read_text()).groups()
|
||||
|
||||
meta = json.loads(meta)
|
||||
meta['package_uuid'] = args.uuid
|
||||
meta = json.dumps(meta, separators=(',', ':'), check_circular=False, ensure_ascii=False)
|
||||
|
||||
update_text_file(args.output, pre + meta + post)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_main(main)
|
|
@ -52,5 +52,6 @@ def main(args):
|
|||
|
||||
print("Generated package {}".format(str(args.output)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_main(main)
|
||||
|
|
|
@ -6,6 +6,7 @@ from taiseilib.common import (
|
|||
)
|
||||
|
||||
from pathlib import Path
|
||||
import fnmatch
|
||||
|
||||
|
||||
def main(args):
|
||||
|
@ -25,9 +26,12 @@ def main(args):
|
|||
|
||||
args = parser.parse_args(args[1:])
|
||||
|
||||
for pattern in args.patterns:
|
||||
for path in args.directory.glob(pattern):
|
||||
print(path.relative_to(args.directory))
|
||||
for path in (p.relative_to(args.directory) for p in args.directory.rglob('*')):
|
||||
path = str(path)
|
||||
for pattern in args.patterns:
|
||||
if fnmatch.fnmatchcase(path, pattern):
|
||||
print(path)
|
||||
break
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -125,3 +125,6 @@ glob_command = [python_thunk, glob_script]
|
|||
|
||||
check_submodules_script = files('check-submodules.py')
|
||||
check_submodules_command = [python_thunk, check_submodules_script]
|
||||
|
||||
em_set_bundle_uuid_script = files('em-set-bundle-uuid.py')
|
||||
em_set_bundle_uuid_command = [python_thunk, em_set_bundle_uuid_script]
|
||||
|
|
|
@ -42,7 +42,7 @@ validation_code = '''if validate_glsl
|
|||
fname = '@0@'.format(src)
|
||||
stage = fname.split('.')[-2]
|
||||
|
||||
glsl_targets += custom_target(fname.underscorify(),
|
||||
spirv = custom_target('SPIRV_' + fname.underscorify(),
|
||||
input : src,
|
||||
output : '@BASENAME@.spv',
|
||||
command : [
|
||||
|
@ -53,6 +53,26 @@ validation_code = '''if validate_glsl
|
|||
build_by_default : true,
|
||||
depfile : '@0@.d'.format(fname.underscorify()),
|
||||
)
|
||||
|
||||
spirv_targets += spirv
|
||||
|
||||
if transpile_glsl
|
||||
essl = custom_target('ESSL_' + fname.underscorify(),
|
||||
input : spirv,
|
||||
output : '@BASENAME@.glsl',
|
||||
command : [
|
||||
spvc_command,
|
||||
'--output', '@OUTPUT@', '@INPUT@',
|
||||
spvc_args,
|
||||
get_variable('spvc_@0@_args'.format(stage)),
|
||||
],
|
||||
install : false,
|
||||
build_by_default : true,
|
||||
depfile : '@0@.d'.format(fname.underscorify()),
|
||||
)
|
||||
|
||||
essl_targets += essl
|
||||
endif
|
||||
endforeach
|
||||
endif'''
|
||||
|
||||
|
|
|
@ -200,4 +200,5 @@ int cli_args(int argc, char **argv, CLIAction *a) {
|
|||
|
||||
void free_cli_action(CLIAction *a) {
|
||||
free(a->filename);
|
||||
a->filename = NULL;
|
||||
}
|
||||
|
|
15
src/config.c
15
src/config.c
|
@ -390,14 +390,24 @@ static bool config_set(const char *key, const char *val, void *data) {
|
|||
typedef void (*ConfigUpgradeFunc)(void);
|
||||
|
||||
static void config_upgrade_1(void) {
|
||||
// disable vsync by default
|
||||
config_set_int(CONFIG_VSYNC, 0);
|
||||
// reset vsync to the default value
|
||||
config_set_int(CONFIG_VSYNC, CONFIG_VSYNC_DEFAULT);
|
||||
|
||||
// this version also changes meaning of the vsync value
|
||||
// previously it was: 0 = on, 1 = off, 2 = adaptive, because lachs0r doesn't know how my absolutely genius options menu works.
|
||||
// now it is: 0 = off, 1 = on, 2 = adaptive, as it should be.
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
static void config_upgrade_2(void) {
|
||||
// emscripten defaults for these have been changed
|
||||
config_set_int(CONFIG_VSYNC, CONFIG_VSYNC_DEFAULT);
|
||||
config_set_int(CONFIG_FXAA, CONFIG_FXAA_DEFAULT);
|
||||
}
|
||||
#else
|
||||
#define config_upgrade_2 NULL
|
||||
#endif
|
||||
|
||||
static ConfigUpgradeFunc config_upgrades[] = {
|
||||
/*
|
||||
To bump the config version and add an upgrade state, simply append an upgrade function to this array.
|
||||
|
@ -408,6 +418,7 @@ static ConfigUpgradeFunc config_upgrades[] = {
|
|||
*/
|
||||
|
||||
config_upgrade_1,
|
||||
config_upgrade_2,
|
||||
};
|
||||
|
||||
static void config_apply_upgrades(int start) {
|
||||
|
|
13
src/config.h
13
src/config.h
|
@ -74,6 +74,15 @@
|
|||
CONFIGDEF_GPKEYBINDING(KEY_SKIP, "gamepad_key_skip", GAMEPAD_BUTTON_B) \
|
||||
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#define CONFIG_VSYNC_DEFAULT 1
|
||||
#define CONFIG_FXAA_DEFAULT 0
|
||||
#else
|
||||
#define CONFIG_VSYNC_DEFAULT 0
|
||||
#define CONFIG_FXAA_DEFAULT 1
|
||||
#endif
|
||||
|
||||
|
||||
#define CONFIGDEFS \
|
||||
/* @version must be on top. don't change its default value here, it does nothing. */ \
|
||||
CONFIGDEF_INT (VERSION, "@version", 0) \
|
||||
|
@ -85,7 +94,7 @@
|
|||
CONFIGDEF_INT (VID_HEIGHT, "vid_height", RESY) \
|
||||
CONFIGDEF_INT (VID_RESIZABLE, "vid_resizable", 0) \
|
||||
CONFIGDEF_INT (VID_FRAMESKIP, "vid_frameskip", 1) \
|
||||
CONFIGDEF_INT (VSYNC, "vsync", 0) \
|
||||
CONFIGDEF_INT (VSYNC, "vsync", CONFIG_VSYNC_DEFAULT) \
|
||||
CONFIGDEF_INT (MIXER_CHUNKSIZE, "mixer_chunksize", 1024) \
|
||||
CONFIGDEF_FLOAT (SFX_VOLUME, "sfx_volume", 0.5) \
|
||||
CONFIGDEF_FLOAT (BGM_VOLUME, "bgm_volume", 1.0) \
|
||||
|
@ -100,7 +109,7 @@
|
|||
CONFIGDEF_INT (SHOT_INVERTED, "shot_inverted", 0) \
|
||||
CONFIGDEF_INT (FOCUS_LOSS_PAUSE, "focus_loss_pause", 1) \
|
||||
CONFIGDEF_INT (PARTICLES, "particles", 1) \
|
||||
CONFIGDEF_INT (FXAA, "fxaa", 1) \
|
||||
CONFIGDEF_INT (FXAA, "fxaa", CONFIG_FXAA_DEFAULT) \
|
||||
CONFIGDEF_INT (POSTPROCESS, "postprocess", 2) \
|
||||
CONFIGDEF_INT (HEALTHBAR_STYLE, "healthbar_style", 1) \
|
||||
CONFIGDEF_INT (SKIP_SPEED, "skip_speed", 10) \
|
||||
|
|
|
@ -27,6 +27,7 @@ static struct {
|
|||
float panelalpha;
|
||||
int end;
|
||||
bool skipable;
|
||||
CallChain cc;
|
||||
} credits;
|
||||
|
||||
#define CREDITS_ENTRY_FADEIN 200.0
|
||||
|
@ -417,7 +418,7 @@ void credits_preload(void) {
|
|||
NULL);
|
||||
}
|
||||
|
||||
static FrameAction credits_logic_frame(void *arg) {
|
||||
static LogicFrameAction credits_logic_frame(void *arg) {
|
||||
update_transition();
|
||||
events_poll(NULL, 0);
|
||||
credits_process();
|
||||
|
@ -432,14 +433,19 @@ static FrameAction credits_logic_frame(void *arg) {
|
|||
}
|
||||
}
|
||||
|
||||
static FrameAction credits_render_frame(void *arg) {
|
||||
static RenderFrameAction credits_render_frame(void *arg) {
|
||||
credits_draw();
|
||||
return RFRAME_SWAP;
|
||||
}
|
||||
|
||||
void credits_loop(void) {
|
||||
static void credits_end_loop(void *ctx) {
|
||||
credits_free();
|
||||
run_call_chain(&credits.cc, NULL);
|
||||
}
|
||||
|
||||
void credits_enter(CallChain next) {
|
||||
credits_preload();
|
||||
credits_init();
|
||||
loop_at_fps(credits_logic_frame, credits_render_frame, NULL, FPS);
|
||||
credits_free();
|
||||
credits.cc = next;
|
||||
eventloop_enter(&credits, credits_logic_frame, credits_render_frame, credits_end_loop, FPS);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
|
||||
#include "taisei.h"
|
||||
|
||||
void credits_loop(void);
|
||||
#include "eventloop/eventloop.h"
|
||||
|
||||
void credits_enter(CallChain next);
|
||||
void credits_preload(void);
|
||||
|
||||
#endif // IGUARD_credits_h
|
||||
|
|
38
src/ending.c
38
src/ending.c
|
@ -13,18 +13,19 @@
|
|||
#include "video.h"
|
||||
#include "progress.h"
|
||||
|
||||
struct EndingEntry {
|
||||
typedef struct EndingEntry {
|
||||
char *msg;
|
||||
Sprite *sprite;
|
||||
int time;
|
||||
};
|
||||
} EndingEntry;
|
||||
|
||||
struct Ending {
|
||||
typedef struct Ending {
|
||||
EndingEntry *entries;
|
||||
int count;
|
||||
int duration;
|
||||
int pos;
|
||||
};
|
||||
CallChain cc;
|
||||
} Ending;
|
||||
|
||||
static void track_ending(int ending) {
|
||||
assert(ending >= 0 && ending < NUM_ENDINGS);
|
||||
|
@ -150,9 +151,7 @@ void good_ending_reimu(Ending *e) {
|
|||
track_ending(ENDING_GOOD_3);
|
||||
}
|
||||
|
||||
static void create_ending(Ending *e) {
|
||||
memset(e, 0, sizeof(Ending));
|
||||
|
||||
static void init_ending(Ending *e) {
|
||||
if(global.plr.continues_used) {
|
||||
global.plr.mode->character->ending.bad(e);
|
||||
} else {
|
||||
|
@ -234,7 +233,7 @@ static bool ending_input_handler(SDL_Event *event, void *arg) {
|
|||
return false;
|
||||
}
|
||||
|
||||
static FrameAction ending_logic_frame(void *arg) {
|
||||
static LogicFrameAction ending_logic_frame(void *arg) {
|
||||
Ending *e = arg;
|
||||
|
||||
update_transition();
|
||||
|
@ -267,7 +266,7 @@ static FrameAction ending_logic_frame(void *arg) {
|
|||
return LFRAME_WAIT;
|
||||
}
|
||||
|
||||
static FrameAction ending_render_frame(void *arg) {
|
||||
static RenderFrameAction ending_render_frame(void *arg) {
|
||||
Ending *e = arg;
|
||||
|
||||
if(e->pos < e->count-1)
|
||||
|
@ -277,13 +276,24 @@ static FrameAction ending_render_frame(void *arg) {
|
|||
return RFRAME_SWAP;
|
||||
}
|
||||
|
||||
void ending_loop(void) {
|
||||
Ending e;
|
||||
static void ending_loop_end(void *ctx) {
|
||||
Ending *e = ctx;
|
||||
CallChain cc = e->cc;
|
||||
free_ending(e);
|
||||
free(e);
|
||||
run_call_chain(&cc, NULL);
|
||||
}
|
||||
|
||||
void ending_enter(CallChain next) {
|
||||
ending_preload();
|
||||
create_ending(&e);
|
||||
|
||||
Ending *e = calloc(1, sizeof(Ending));
|
||||
init_ending(e);
|
||||
e->cc = next;
|
||||
|
||||
global.frames = 0;
|
||||
set_ortho(SCREEN_W, SCREEN_H);
|
||||
start_bgm("ending");
|
||||
loop_at_fps(ending_logic_frame, ending_render_frame, &e, FPS);
|
||||
free_ending(&e);
|
||||
|
||||
eventloop_enter(e, ending_logic_frame, ending_render_frame, ending_loop_end, FPS);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "resource/texture.h"
|
||||
#include "eventloop/eventloop.h"
|
||||
|
||||
enum {
|
||||
ENDING_FADE_OUT = 200,
|
||||
|
@ -56,10 +56,9 @@ static inline attr_must_inline bool ending_is_bad(EndingID end) {
|
|||
#undef ENDING
|
||||
}
|
||||
|
||||
typedef struct EndingEntry EndingEntry;
|
||||
typedef struct Ending Ending;
|
||||
|
||||
void ending_loop(void);
|
||||
void ending_enter(CallChain next);
|
||||
void ending_preload(void);
|
||||
|
||||
/*
|
||||
|
|
114
src/eventloop/eventloop.c
Normal file
114
src/eventloop/eventloop.c
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT-License
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "eventloop_private.h"
|
||||
#include "util.h"
|
||||
#include "global.h"
|
||||
#include "video.h"
|
||||
|
||||
struct evloop_s evloop;
|
||||
|
||||
void eventloop_enter(void *context, LogicFrameFunc frame_logic, RenderFrameFunc frame_render, PostLoopFunc on_leave, uint target_fps) {
|
||||
assert(is_main_thread());
|
||||
assume(evloop.stack_ptr < evloop.stack + EVLOOP_STACK_SIZE - 1);
|
||||
|
||||
LoopFrame *frame;
|
||||
|
||||
if(evloop.stack_ptr == NULL) {
|
||||
frame = evloop.stack_ptr = evloop.stack;
|
||||
} else {
|
||||
frame = ++evloop.stack_ptr;
|
||||
}
|
||||
|
||||
frame->context = context;
|
||||
frame->logic = frame_logic;
|
||||
frame->render = frame_render;
|
||||
frame->on_leave = on_leave;
|
||||
frame->frametime = HRTIME_RESOLUTION / target_fps;
|
||||
frame->prev_logic_action = LFRAME_WAIT;
|
||||
}
|
||||
|
||||
void eventloop_leave(void) {
|
||||
assert(is_main_thread());
|
||||
assume(evloop.stack_ptr != NULL);
|
||||
|
||||
LoopFrame *frame = evloop.stack_ptr;
|
||||
|
||||
if(evloop.stack_ptr == evloop.stack) {
|
||||
evloop.stack_ptr = NULL;
|
||||
} else {
|
||||
--evloop.stack_ptr;
|
||||
}
|
||||
|
||||
if(frame->on_leave != NULL) {
|
||||
frame->on_leave(frame->context);
|
||||
}
|
||||
}
|
||||
|
||||
LogicFrameAction run_logic_frame(LoopFrame *frame) {
|
||||
assert(frame == evloop.stack_ptr);
|
||||
|
||||
if(frame->prev_logic_action == LFRAME_STOP) {
|
||||
return LFRAME_STOP;
|
||||
}
|
||||
|
||||
LogicFrameAction a = frame->logic(frame->context);
|
||||
|
||||
if(taisei_quit_requested()) {
|
||||
a = LFRAME_STOP;
|
||||
}
|
||||
|
||||
frame->prev_logic_action = a;
|
||||
return a;
|
||||
}
|
||||
|
||||
LogicFrameAction handle_logic(LoopFrame **pframe, const FrameTimes *ftimes) {
|
||||
LogicFrameAction lframe_action;
|
||||
uint cnt = 0;
|
||||
|
||||
do {
|
||||
lframe_action = run_logic_frame(*pframe);
|
||||
|
||||
while(evloop.stack_ptr != *pframe) {
|
||||
*pframe = evloop.stack_ptr;
|
||||
lframe_action = run_logic_frame(*pframe);
|
||||
cnt = UINT_MAX; // break out of the outer loop
|
||||
}
|
||||
} while(
|
||||
lframe_action == LFRAME_SKIP &&
|
||||
++cnt < config_get_int(CONFIG_SKIP_SPEED)
|
||||
);
|
||||
|
||||
if(lframe_action == LFRAME_STOP) {
|
||||
eventloop_leave();
|
||||
*pframe = evloop.stack_ptr;
|
||||
|
||||
if(*pframe == NULL) {
|
||||
return LFRAME_STOP;
|
||||
}
|
||||
}
|
||||
|
||||
fpscounter_update(&global.fps.logic);
|
||||
return lframe_action;
|
||||
}
|
||||
|
||||
RenderFrameAction run_render_frame(LoopFrame *frame) {
|
||||
attr_unused LoopFrame *stack_prev = evloop.stack_ptr;
|
||||
r_framebuffer_clear(NULL, CLEAR_ALL, RGBA(0, 0, 0, 1), 1);
|
||||
RenderFrameAction a = frame->render(frame->context);
|
||||
assert(evloop.stack_ptr == stack_prev);
|
||||
|
||||
if(a == RFRAME_SWAP) {
|
||||
video_swap_buffers();
|
||||
}
|
||||
|
||||
fpscounter_update(&global.fps.render);
|
||||
return a;
|
||||
}
|
93
src/eventloop/eventloop.h
Normal file
93
src/eventloop/eventloop.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT-License
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#ifndef IGUARD_eventloop_eventloop_h
|
||||
#define IGUARD_eventloop_eventloop_h
|
||||
|
||||
#include "taisei.h"
|
||||
|
||||
typedef enum RenderFrameAction {
|
||||
RFRAME_SWAP,
|
||||
RFRAME_DROP,
|
||||
} RenderFrameAction;
|
||||
|
||||
typedef enum LogicFrameAction {
|
||||
LFRAME_WAIT,
|
||||
LFRAME_SKIP,
|
||||
LFRAME_STOP,
|
||||
} LogicFrameAction;
|
||||
|
||||
typedef LogicFrameAction (*LogicFrameFunc)(void *context);
|
||||
typedef RenderFrameAction (*RenderFrameFunc)(void *context);
|
||||
typedef void (*PostLoopFunc)(void *context);
|
||||
|
||||
#ifdef DEBUG_CALLCHAIN
|
||||
#include "util/debug.h"
|
||||
#include "log.h"
|
||||
#endif
|
||||
|
||||
typedef struct CallChainResult {
|
||||
void *ctx;
|
||||
void *result;
|
||||
} CallChainResult;
|
||||
|
||||
typedef struct CallChain {
|
||||
void (*callback)(CallChainResult result);
|
||||
void *ctx;
|
||||
#ifdef DEBUG_CALLCHAIN
|
||||
DebugInfo _debug_;
|
||||
#endif
|
||||
} CallChain;
|
||||
|
||||
#ifdef DEBUG_CALLCHAIN
|
||||
#define CALLCHAIN(callback, ctx) ((CallChain) { (callback), (ctx), _DEBUG_INFO_INITIALIZER_ })
|
||||
#else
|
||||
#define CALLCHAIN(callback, ctx) ((CallChain) { (callback), (ctx) })
|
||||
#endif
|
||||
|
||||
#define NO_CALLCHAIN CALLCHAIN(NULL, NULL)
|
||||
|
||||
#define CALLCHAIN_RESULT(ctx, result) ((CallChainResult) { (ctx), (result) })
|
||||
|
||||
void eventloop_enter(
|
||||
void *context,
|
||||
LogicFrameFunc frame_logic,
|
||||
RenderFrameFunc frame_render,
|
||||
PostLoopFunc on_leave,
|
||||
uint target_fps
|
||||
) attr_nonnull(1, 2, 3);
|
||||
|
||||
void eventloop_run(void);
|
||||
|
||||
#ifdef DEBUG_CALLCHAIN
|
||||
attr_must_inline
|
||||
static inline void run_call_chain(CallChain *cc, void *result, DebugInfo caller_dbg) {
|
||||
if(cc->callback != NULL) {
|
||||
log_debug("Calling CC set in %s (%s:%u)",
|
||||
cc->_debug_.func, cc->_debug_.file, cc->_debug_.line);
|
||||
log_debug(" from %s (%s:%u)",
|
||||
caller_dbg.func, caller_dbg.file, caller_dbg.line);
|
||||
cc->callback(CALLCHAIN_RESULT(cc->ctx, result));
|
||||
} else {
|
||||
log_debug("Dead end at %s (%s:%u)",
|
||||
caller_dbg.func, caller_dbg.file, caller_dbg.line
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#define run_call_chain(cc, result) run_call_chain(cc, result, _DEBUG_INFO_)
|
||||
#else
|
||||
attr_must_inline
|
||||
static inline void run_call_chain(CallChain *cc, void *result) {
|
||||
if(cc->callback != NULL) {
|
||||
cc->callback(CALLCHAIN_RESULT(cc->ctx, result));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // IGUARD_eventloop_eventloop_h
|
47
src/eventloop/eventloop_private.h
Normal file
47
src/eventloop/eventloop_private.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT-License
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@alienslab.net>.
|
||||
*/
|
||||
|
||||
#ifndef IGUARD_eventloop_eventloop_private_h
|
||||
#define IGUARD_eventloop_eventloop_private_h
|
||||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "eventloop.h"
|
||||
#include "hirestime.h"
|
||||
|
||||
typedef struct LoopFrame LoopFrame;
|
||||
|
||||
#define EVLOOP_STACK_SIZE 32
|
||||
|
||||
struct LoopFrame {
|
||||
void *context;
|
||||
LogicFrameFunc logic;
|
||||
RenderFrameFunc render;
|
||||
PostLoopFunc on_leave;
|
||||
hrtime_t frametime;
|
||||
LogicFrameAction prev_logic_action;
|
||||