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;
|
||||
};
|
||||
|
||||
extern struct evloop_s {
|
||||
LoopFrame stack[EVLOOP_STACK_SIZE];
|
||||
LoopFrame *stack_ptr;
|
||||
} evloop;
|
||||
|
||||
typedef struct FrameTimes {
|
||||
hrtime_t target;
|
||||
hrtime_t start;
|
||||
hrtime_t next;
|
||||
} FrameTimes;
|
||||
|
||||
void eventloop_leave(void);
|
||||
|
||||
LogicFrameAction run_logic_frame(LoopFrame *frame);
|
||||
LogicFrameAction handle_logic(LoopFrame **pframe, const FrameTimes *ftimes);
|
||||
RenderFrameAction run_render_frame(LoopFrame *frame);
|
||||
|
||||
#endif // IGUARD_eventloop_eventloop_private_h
|
105
src/eventloop/executor_emscripten.c
Normal file
105
src/eventloop/executor_emscripten.c
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 "events.h"
|
||||
#include "global.h"
|
||||
#include <emscripten.h>
|
||||
|
||||
static FrameTimes frame_times;
|
||||
static uint frame_num;
|
||||
|
||||
static bool em_handle_resize_event(SDL_Event *event, void *arg);
|
||||
|
||||
static void em_loop_callback(void) {
|
||||
LoopFrame *frame = evloop.stack_ptr;
|
||||
|
||||
if(!frame) {
|
||||
events_unregister_handler(em_handle_resize_event);
|
||||
emscripten_cancel_main_loop();
|
||||
return;
|
||||
}
|
||||
|
||||
if(time_get() < frame_times.next) {
|
||||
return;
|
||||
}
|
||||
|
||||
frame_times.start = time_get();
|
||||
frame_times.target = frame->frametime;
|
||||
|
||||
frame_times.next += frame_times.target;
|
||||
hrtime_t min_next_time = frame_times.start - 2 * frame_times.target;
|
||||
|
||||
if(min_next_time > frame_times.next) {
|
||||
frame_times.next = min_next_time;
|
||||
}
|
||||
|
||||
global.fps.busy.last_update_time = frame_times.start;
|
||||
|
||||
LogicFrameAction lframe_action = handle_logic(&frame, &frame_times);
|
||||
|
||||
if(!frame || lframe_action == LFRAME_STOP) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!(frame_num++ % get_effective_frameskip())) {
|
||||
run_render_frame(frame);
|
||||
}
|
||||
|
||||
fpscounter_update(&global.fps.busy);
|
||||
}
|
||||
|
||||
static void update_vsync(void) {
|
||||
switch(config_get_int(CONFIG_VSYNC)) {
|
||||
case 0: r_vsync(VSYNC_NONE); break;
|
||||
default: r_vsync(VSYNC_NORMAL); break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool em_handle_resize_event(SDL_Event *event, void *arg) {
|
||||
emscripten_cancel_main_loop();
|
||||
emscripten_set_main_loop(em_loop_callback, 0, false);
|
||||
update_vsync();
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool em_audio_workaround(SDL_Event *event, void *arg) {
|
||||
// Workaround for Chromium:
|
||||
// https://github.com/emscripten-core/emscripten/issues/6511
|
||||
// Will start playing audio as soon as the first input occurs.
|
||||
|
||||
(__extension__ EM_ASM({
|
||||
var audioctx = Module['SDL2'].audioContext;
|
||||
if(audioctx !== undefined) {
|
||||
audioctx.resume();
|
||||
}
|
||||
}));
|
||||
|
||||
events_unregister_handler(em_audio_workaround);
|
||||
return false;
|
||||
}
|
||||
|
||||
void eventloop_run(void) {
|
||||
frame_times.next = time_get();
|
||||
emscripten_set_main_loop(em_loop_callback, 0, false);
|
||||
update_vsync();
|
||||
|
||||
events_register_handler(&(EventHandler) {
|
||||
em_audio_workaround, NULL, EPRIO_SYSTEM, SDL_KEYDOWN,
|
||||
});
|
||||
|
||||
events_register_handler(&(EventHandler) {
|
||||
em_handle_resize_event, NULL, EPRIO_SYSTEM, MAKE_TAISEI_EVENT(TE_VIDEO_MODE_CHANGED)
|
||||
});
|
||||
|
||||
(__extension__ EM_ASM({
|
||||
Module['onFirstFrame']();
|
||||
}));
|
||||
}
|
139
src/eventloop/executor_synchro.c
Normal file
139
src/eventloop/executor_synchro.c
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* 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 "framerate.h"
|
||||
#include "global.h"
|
||||
|
||||
void eventloop_run(void) {
|
||||
assert(is_main_thread());
|
||||
|
||||
if(evloop.stack_ptr == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
LoopFrame *frame = evloop.stack_ptr;
|
||||
FrameTimes frame_times;
|
||||
frame_times.target = frame->frametime;
|
||||
frame_times.start = time_get();
|
||||
frame_times.next = frame_times.start + frame_times.target;
|
||||
int32_t sleep = env_get("TAISEI_FRAMELIMITER_SLEEP", 3);
|
||||
bool compensate = env_get("TAISEI_FRAMELIMITER_COMPENSATE", 1);
|
||||
bool uncapped_rendering_env, uncapped_rendering;
|
||||
|
||||
if(global.is_replay_verification) {
|
||||
uncapped_rendering_env = false;
|
||||
} else {
|
||||
uncapped_rendering_env = env_get("TAISEI_FRAMELIMITER_LOGIC_ONLY", 0);
|
||||
}
|
||||
|
||||
uncapped_rendering = uncapped_rendering_env;
|
||||
uint32_t frame_num = 0;
|
||||
|
||||
begin_main_loop:
|
||||
while(frame != NULL) {
|
||||
|
||||
#ifdef DEBUG
|
||||
if(uncapped_rendering_env) {
|
||||
uncapped_rendering = !gamekeypressed(KEY_FPSLIMIT_OFF);
|
||||
}
|
||||
#endif
|
||||
|
||||
frame_times.start = time_get();
|
||||
|
||||
begin_frame:
|
||||
global.fps.busy.last_update_time = time_get();
|
||||
frame_times.target = frame->frametime;
|
||||
++frame_num;
|
||||
|
||||
LogicFrameAction lframe_action = LFRAME_WAIT;
|
||||
|
||||
if(uncapped_rendering) {
|
||||
attr_unused uint32_t logic_frames = 0;
|
||||
|
||||
while(lframe_action != LFRAME_STOP && frame_times.next < frame_times.start) {
|
||||
lframe_action = handle_logic(&frame, &frame_times);
|
||||
|
||||
if(!frame || lframe_action == LFRAME_STOP) {
|
||||
goto begin_main_loop;
|
||||
}
|
||||
|
||||
++logic_frames;
|
||||
hrtime_t total = time_get() - frame_times.start;
|
||||
|
||||
if(total > frame_times.target) {
|
||||
frame_times.next = frame_times.start;
|
||||
log_debug("Executing logic took too long (%"PRIuTIME"), giving up", total);
|
||||
} else {
|
||||
frame_times.next += frame_times.target;
|
||||
}
|
||||
}
|
||||
|
||||
if(logic_frames > 1) {
|
||||
log_debug(
|
||||
"Dropped %u logic frame%s in superframe #%u",
|
||||
logic_frames - 1,
|
||||
logic_frames > 2 ? "s" : "",
|
||||
frame_num
|
||||
);
|
||||
}
|
||||
} else {
|
||||
lframe_action = handle_logic(&frame, &frame_times);
|
||||
|
||||
if(!frame || lframe_action == LFRAME_STOP) {
|
||||
goto begin_main_loop;
|
||||
}
|
||||
}
|
||||
|
||||
if((uncapped_rendering || !(frame_num % get_effective_frameskip())) && !global.is_replay_verification) {
|
||||
run_render_frame(frame);
|
||||
}
|
||||
|
||||
fpscounter_update(&global.fps.busy);
|
||||
|
||||
if(uncapped_rendering || global.frameskip > 0 || global.is_replay_verification) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if(gamekeypressed(KEY_FPSLIMIT_OFF)) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
frame_times.next = frame_times.start + frame_times.target;
|
||||
|
||||
if(compensate) {
|
||||
hrtime_t rt = time_get();
|
||||
|
||||
if(rt > frame_times.next) {
|
||||
// frame took too long...
|
||||
// try to compensate in the next frame to avoid slowdown
|
||||
frame_times.start = rt - imin(rt - frame_times.next, frame_times.target);
|
||||
goto begin_frame;
|
||||
}
|
||||
}
|
||||
|
||||
if(sleep > 0) {
|
||||
// CAUTION: All of these casts are important!
|
||||
while((shrtime_t)frame_times.next - (shrtime_t)time_get() > (shrtime_t)frame_times.target / sleep) {
|
||||
uint32_t nap_multiplier = 1;
|
||||
uint32_t nap_divisor = 3;
|
||||
hrtime_t nap_raw = imax(0, (shrtime_t)frame_times.next - (shrtime_t)time_get());
|
||||
uint32_t nap_sdl = (nap_multiplier * nap_raw * 1000) / (HRTIME_RESOLUTION * nap_divisor);
|
||||
nap_sdl = imax(nap_sdl, 1);
|
||||
SDL_Delay(nap_sdl);
|
||||
}
|
||||
}
|
||||
|
||||
while(time_get() < frame_times.next);
|
||||
}
|
||||
}
|
14
src/eventloop/meson.build
Normal file
14
src/eventloop/meson.build
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
eventloop_src = files(
|
||||
'eventloop.c',
|
||||
)
|
||||
|
||||
if host_machine.system() == 'emscripten'
|
||||
eventloop_src += files(
|
||||
'executor_emscripten.c',
|
||||
)
|
||||
else
|
||||
eventloop_src += files(
|
||||
'executor_synchro.c',
|
||||
)
|
||||
endif
|
72
src/events.c
72
src/events.c
|
@ -299,8 +299,74 @@ static bool events_handler_hotkeys(SDL_Event *event, void *arg);
|
|||
static bool events_handler_key_down(SDL_Event *event, void *arg);
|
||||
static bool events_handler_key_up(SDL_Event *event, void *arg);
|
||||
|
||||
attr_unused
|
||||
static bool events_handler_debug_winevt(SDL_Event *event, void *arg) {
|
||||
// copied from SDL wiki almost verbatim
|
||||
|
||||
if(event->type == SDL_WINDOWEVENT) {
|
||||
switch(event->window.event) {
|
||||
case SDL_WINDOWEVENT_SHOWN:
|
||||
log_info("Window %d shown", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_HIDDEN:
|
||||
log_info("Window %d hidden", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_EXPOSED:
|
||||
log_info("Window %d exposed", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MOVED:
|
||||
log_info("Window %d moved to %d,%d", event->window.windowID, event->window.data1, event->window.data2);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
log_info("Window %d resized to %dx%d", event->window.windowID, event->window.data1, event->window.data2);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
log_info("Window %d size changed to %dx%d", event->window.windowID, event->window.data1, event->window.data2);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MINIMIZED:
|
||||
log_info("Window %d minimized", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_MAXIMIZED:
|
||||
log_info("Window %d maximized", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESTORED:
|
||||
log_info("Window %d restored", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_ENTER:
|
||||
log_info("Mouse entered window %d", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_LEAVE:
|
||||
log_info("Mouse left window %d", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
log_info("Window %d gained keyboard focus", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
log_info("Window %d lost keyboard focus", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
log_info("Window %d closed", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_TAKE_FOCUS:
|
||||
log_info("Window %d is offered a focus", event->window.windowID);
|
||||
break;
|
||||
case SDL_WINDOWEVENT_HIT_TEST:
|
||||
log_info("Window %d has a special hit test", event->window.windowID);
|
||||
break;
|
||||
default:
|
||||
log_warn("Window %d got unknown event %d", event->window.windowID, event->window.event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static EventHandler default_handlers[] = {
|
||||
#ifdef DEBUG_WINDOW_EVENTS
|
||||
{ .proc = events_handler_debug_winevt, .priority = EPRIO_SYSTEM, .event_type = SDL_WINDOWEVENT },
|
||||
#endif
|
||||
{ .proc = events_handler_quit, .priority = EPRIO_SYSTEM, .event_type = SDL_QUIT },
|
||||
{ .proc = events_handler_config, .priority = EPRIO_SYSTEM, .event_type = 0 },
|
||||
{ .proc = events_handler_keyrepeat_workaround, .priority = EPRIO_CAPTURE, .event_type = 0 },
|
||||
|
@ -394,6 +460,10 @@ static bool events_handler_key_down(SDL_Event *event, void *arg) {
|
|||
SDL_Scancode scan = event->key.keysym.scancode;
|
||||
bool repeat = event->key.repeat;
|
||||
|
||||
if(video.backend == VIDEO_BACKEND_EMSCRIPTEN && scan == SDL_SCANCODE_TAB) {
|
||||
scan = SDL_SCANCODE_ESCAPE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Emit menu events
|
||||
*/
|
||||
|
@ -477,7 +547,7 @@ static bool events_handler_hotkeys(SDL_Event *event, void *arg) {
|
|||
}
|
||||
|
||||
if((scan == SDL_SCANCODE_RETURN && (mod & KMOD_ALT)) || scan == config_get_int(CONFIG_KEY_FULLSCREEN)) {
|
||||
config_set_int(CONFIG_FULLSCREEN, !config_get_int(CONFIG_FULLSCREEN));
|
||||
video_set_fullscreen(!video_is_fullscreen());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
160
src/framerate.c
160
src/framerate.c
|
@ -54,163 +54,3 @@ uint32_t get_effective_frameskip(void) {
|
|||
|
||||
return frameskip;
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
#define SDL_Delay emscripten_sleep
|
||||
#endif
|
||||
|
||||
void loop_at_fps(LogicFrameFunc logic_frame, RenderFrameFunc render_frame, void *arg, uint32_t fps) {
|
||||
assert(logic_frame != NULL);
|
||||
assert(render_frame != NULL);
|
||||
assert(fps > 0);
|
||||
|
||||
hrtime_t frame_start_time = time_get();
|
||||
hrtime_t next_frame_time = frame_start_time;
|
||||
hrtime_t target_frame_time = HRTIME_RESOLUTION / fps;
|
||||
|
||||
FrameAction rframe_action = RFRAME_SWAP;
|
||||
FrameAction lframe_action = LFRAME_WAIT;
|
||||
|
||||
int32_t sleep = env_get("TAISEI_FRAMELIMITER_SLEEP", 3);
|
||||
bool compensate = env_get("TAISEI_FRAMELIMITER_COMPENSATE", 1);
|
||||
bool uncapped_rendering_env = env_get("TAISEI_FRAMELIMITER_LOGIC_ONLY", 0);
|
||||
|
||||
if(global.is_replay_verification) {
|
||||
uncapped_rendering_env = false;
|
||||
}
|
||||
|
||||
uint32_t frame_num = 0;
|
||||
|
||||
// don't care about thread safety, we can render only on the main thread anyway
|
||||
static uint8_t recursion_detector;
|
||||
++recursion_detector;
|
||||
|
||||
while(true) {
|
||||
bool uncapped_rendering = uncapped_rendering_env;
|
||||
frame_start_time = time_get();
|
||||
|
||||
begin_frame:
|
||||
|
||||
global.fps.busy.last_update_time = time_get();
|
||||
|
||||
++frame_num;
|
||||
|
||||
if(uncapped_rendering) {
|
||||
uint32_t logic_frames = 0;
|
||||
|
||||
while(lframe_action != LFRAME_STOP && next_frame_time < frame_start_time) {
|
||||
uint8_t rval = recursion_detector;
|
||||
|
||||
lframe_action = logic_frame(arg);
|
||||
fpscounter_update(&global.fps.logic);
|
||||
++logic_frames;
|
||||
|
||||
if(rval != recursion_detector) {
|
||||
log_debug(
|
||||
"Recursive call detected (%u != %u), resetting next frame time to avoid a large skip",
|
||||
rval,
|
||||
recursion_detector
|
||||
);
|
||||
next_frame_time = time_get() + target_frame_time;
|
||||
break;
|
||||
}
|
||||
|
||||
hrtime_t frametime = target_frame_time;
|
||||
|
||||
if(lframe_action == LFRAME_SKIP) {
|
||||
frametime /= imax(1, config_get_int(CONFIG_SKIP_SPEED));
|
||||
}
|
||||
|
||||
next_frame_time += frametime;
|
||||
|
||||
hrtime_t total = time_get() - frame_start_time;
|
||||
|
||||
if(total > target_frame_time) {
|
||||
next_frame_time = frame_start_time;
|
||||
log_debug("Executing logic took too long (%"PRIuTIME"), giving up", total);
|
||||
}
|
||||
}
|
||||
|
||||
if(logic_frames > 1) {
|
||||
log_debug(
|
||||
"Dropped %u logic frame%s in superframe #%u",
|
||||
logic_frames - 1,
|
||||
logic_frames > 2 ? "s" : "",
|
||||
frame_num
|
||||
);
|
||||
}
|
||||
} else {
|
||||
uint cnt = 0;
|
||||
|
||||
do {
|
||||
lframe_action = logic_frame(arg);
|
||||
fpscounter_update(&global.fps.logic);
|
||||
} while(lframe_action == LFRAME_SKIP && ++cnt < config_get_int(CONFIG_SKIP_SPEED));
|
||||
}
|
||||
|
||||
if(taisei_quit_requested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if((!uncapped_rendering && frame_num % get_effective_frameskip()) || global.is_replay_verification) {
|
||||
rframe_action = RFRAME_DROP;
|
||||
} else {
|
||||
r_framebuffer_clear(NULL, CLEAR_ALL, RGBA(0, 0, 0, 1), 1);
|
||||
rframe_action = render_frame(arg);
|
||||
fpscounter_update(&global.fps.render);
|
||||
}
|
||||
|
||||
if(rframe_action == RFRAME_SWAP) {
|
||||
video_swap_buffers();
|
||||
}
|
||||
|
||||
if(lframe_action == LFRAME_STOP) {
|
||||
break;
|
||||
}
|
||||
|
||||
fpscounter_update(&global.fps.busy);
|
||||
|
||||
if(uncapped_rendering || global.frameskip > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if(gamekeypressed(KEY_FPSLIMIT_OFF)) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
next_frame_time = frame_start_time + target_frame_time;
|
||||
// next_frame_time = frame_start_time + 2 * target_frame_time - global.fps.logic.frametime;
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
SDL_Delay(1);
|
||||
#endif
|
||||
|
||||
if(compensate) {
|
||||
hrtime_t rt = time_get();
|
||||
|
||||
if(rt > next_frame_time) {
|
||||
// frame took too long...
|
||||
// try to compensate in the next frame to avoid slowdown
|
||||
frame_start_time = rt - imin(rt - next_frame_time, target_frame_time);
|
||||
goto begin_frame;
|
||||
}
|
||||
}
|
||||
|
||||
if(sleep > 0) {
|
||||
// CAUTION: All of these casts are important!
|
||||
while((shrtime_t)next_frame_time - (shrtime_t)time_get() > (shrtime_t)target_frame_time / sleep) {
|
||||
uint32_t nap_multiplier = 1;
|
||||
uint32_t nap_divisor = 3;
|
||||
hrtime_t nap_raw = imax(0, (shrtime_t)next_frame_time - (shrtime_t)time_get());
|
||||
uint32_t nap_sdl = (nap_multiplier * nap_raw * 1000) / (HRTIME_RESOLUTION * nap_divisor);
|
||||
nap_sdl = imax(nap_sdl, 1);
|
||||
SDL_Delay(nap_sdl);
|
||||
}
|
||||
}
|
||||
|
||||
while(time_get() < next_frame_time);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,20 +20,7 @@ typedef struct {
|
|||
hrtime_t last_update_time; // internal; last time the average was recalculated
|
||||
} FPSCounter;
|
||||
|
||||
typedef enum FrameAction {
|
||||
RFRAME_SWAP,
|
||||
RFRAME_DROP,
|
||||
|
||||
LFRAME_WAIT,
|
||||
LFRAME_SKIP,
|
||||
LFRAME_STOP,
|
||||
} FrameAction;
|
||||
|
||||
typedef FrameAction (*LogicFrameFunc)(void*);
|
||||
typedef FrameAction (*RenderFrameFunc)(void*);
|
||||
|
||||
uint32_t get_effective_frameskip(void);
|
||||
void loop_at_fps(LogicFrameFunc logic_frame, RenderFrameFunc render_frame, void *arg, uint32_t fps);
|
||||
void fpscounter_reset(FPSCounter *fps);
|
||||
void fpscounter_update(FPSCounter *fps);
|
||||
|
||||
|
|
|
@ -55,3 +55,9 @@ void taisei_quit(void) {
|
|||
bool taisei_quit_requested(void) {
|
||||
return SDL_AtomicGet(&quitting);
|
||||
}
|
||||
|
||||
void taisei_commit_persistent_data(void) {
|
||||
config_save();
|
||||
progress_save();
|
||||
vfs_sync(VFS_SYNC_STORE, NO_CALLCHAIN);
|
||||
}
|
||||
|
|
|
@ -138,6 +138,7 @@ void init_global(CLIAction *cli);
|
|||
|
||||
void taisei_quit(void);
|
||||
bool taisei_quit_requested(void);
|
||||
void taisei_commit_persistent_data(void);
|
||||
|
||||
// XXX: Move this somewhere?
|
||||
bool gamekeypressed(KeyIndex key);
|
||||
|
|
|
@ -19,13 +19,13 @@ typedef struct ListAnchorInterface ListAnchorInterface;
|
|||
typedef struct ListAnchor ListAnchor;
|
||||
typedef struct ListContainer ListContainer;
|
||||
|
||||
#define LIST_INTERFACE_BASE(typename) LIST_ALIGN struct { \
|
||||
#define LIST_INTERFACE_BASE(typename) struct { \
|
||||
typename *next; \
|
||||
typename *prev; \
|
||||
}
|
||||
|
||||
#define LIST_INTERFACE(typename) union { \
|
||||
ListInterface list_interface; \
|
||||
LIST_ALIGN ListInterface list_interface; \
|
||||
LIST_INTERFACE_BASE(typename); \
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,8 @@ typedef enum LogLevel {
|
|||
#ifndef LOG_DEFAULT_LEVELS_CONSOLE
|
||||
#ifdef DEBUG
|
||||
#define LOG_DEFAULT_LEVELS_CONSOLE LOG_ALL
|
||||
#elif defined __EMSCRIPTEN__
|
||||
#define LOG_DEFAULT_LEVELS_CONSOLE LOG_ALERT | LOG_INFO
|
||||
#else
|
||||
#define LOG_DEFAULT_LEVELS_CONSOLE LOG_ALERT
|
||||
#endif
|
||||
|
|
273
src/main.c
273
src/main.c
|
@ -24,9 +24,9 @@
|
|||
#include "vfs/setup.h"
|
||||
#include "version.h"
|
||||
#include "credits.h"
|
||||
#include "renderer/api.h"
|
||||
#include "taskmanager.h"
|
||||
|
||||
attr_unused
|
||||
static void taisei_shutdown(void) {
|
||||
log_info("Shutting down");
|
||||
|
||||
|
@ -144,76 +144,82 @@ static void log_system_specs(void) {
|
|||
log_info("RAM: %d MB", SDL_GetSystemRAM());
|
||||
}
|
||||
|
||||
static void log_version(void) {
|
||||
log_info("%s %s", TAISEI_VERSION_FULL, TAISEI_VERSION_BUILD_TYPE);
|
||||
}
|
||||
|
||||
typedef struct MainContext {
|
||||
CLIAction cli;
|
||||
Replay replay;
|
||||
int replay_idx;
|
||||
uchar headless : 1;
|
||||
} MainContext;
|
||||
|
||||
static void main_post_vfsinit(CallChainResult ccr);
|
||||
static void main_singlestg(MainContext *mctx) attr_unused;
|
||||
static void main_replay(MainContext *mctx);
|
||||
static noreturn void main_vfstree(CallChainResult ccr);
|
||||
|
||||
static noreturn void main_quit(MainContext *ctx, int status) {
|
||||
free_cli_action(&ctx->cli);
|
||||
replay_destroy(&ctx->replay);
|
||||
free(ctx);
|
||||
exit(status);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
MainContext *ctx = calloc(1, sizeof(*ctx));
|
||||
|
||||
setlocale(LC_ALL, "C");
|
||||
|
||||
Replay replay = {0};
|
||||
int replay_idx = 0;
|
||||
bool headless = false;
|
||||
|
||||
htutil_init();
|
||||
init_log();
|
||||
|
||||
stage_init_array(); // cli_args depends on this
|
||||
|
||||
// commandline arguments should be parsed as early as possible
|
||||
CLIAction a;
|
||||
cli_args(argc, argv, &a); // stage_init_array goes first!
|
||||
cli_args(argc, argv, &ctx->cli); // stage_init_array goes first!
|
||||
|
||||
if(a.type == CLI_Quit) {
|
||||
free_cli_action(&a);
|
||||
return 1;
|
||||
if(ctx->cli.type == CLI_Quit) {
|
||||
main_quit(ctx, 0);
|
||||
}
|
||||
|
||||
if(a.type == CLI_DumpStages) {
|
||||
if(ctx->cli.type == CLI_DumpStages) {
|
||||
for(StageInfo *stg = stages; stg->procs; ++stg) {
|
||||
tsfprintf(stdout, "%X %s: %s\n", stg->id, stg->title, stg->subtitle);
|
||||
}
|
||||
|
||||
free_cli_action(&a);
|
||||
return 0;
|
||||
} else if(a.type == CLI_PlayReplay || a.type == CLI_VerifyReplay) {
|
||||
if(!replay_load_syspath(&replay, a.filename, REPLAY_READ_ALL)) {
|
||||
free_cli_action(&a);
|
||||
return 1;
|
||||
}
|
||||
|
||||
replay_idx = a.stageid ? replay_find_stage_idx(&replay, a.stageid) : 0;
|
||||
|
||||
if(replay_idx < 0) {
|
||||
free_cli_action(&a);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(a.type == CLI_VerifyReplay) {
|
||||
headless = true;
|
||||
}
|
||||
} else if(a.type == CLI_DumpVFSTree) {
|
||||
vfs_setup(false);
|
||||
|
||||
SDL_RWops *rwops = SDL_RWFromFP(stdout, false);
|
||||
int status = 0;
|
||||
|
||||
if(!rwops) {
|
||||
log_fatal("SDL_RWFromFP() failed: %s", SDL_GetError());
|
||||
log_sdl_error(LOG_ERROR, "SDL_RWFromFP");
|
||||
status = 1;
|
||||
} else if(!vfs_print_tree(rwops, a.filename)) {
|
||||
log_error("VFS error: %s", vfs_get_error());
|
||||
status = 2;
|
||||
}
|
||||
|
||||
SDL_RWclose(rwops);
|
||||
vfs_shutdown();
|
||||
free_cli_action(&a);
|
||||
return status;
|
||||
main_quit(ctx, 0);
|
||||
}
|
||||
|
||||
free_cli_action(&a);
|
||||
if(ctx->cli.type == CLI_PlayReplay || ctx->cli.type == CLI_VerifyReplay) {
|
||||
if(!replay_load_syspath(&ctx->replay, ctx->cli.filename, REPLAY_READ_ALL)) {
|
||||
main_quit(ctx, 1);
|
||||
}
|
||||
|
||||
vfs_setup(false);
|
||||
ctx->replay_idx = ctx->cli.stageid ? replay_find_stage_idx(&ctx->replay, ctx->cli.stageid) : 0;
|
||||
|
||||
if(headless) {
|
||||
if(ctx->replay_idx < 0) {
|
||||
main_quit(ctx, 1);
|
||||
}
|
||||
|
||||
if(ctx->cli.type == CLI_VerifyReplay) {
|
||||
ctx->headless = true;
|
||||
}
|
||||
} else if(ctx->cli.type == CLI_DumpVFSTree) {
|
||||
vfs_setup(CALLCHAIN(main_vfstree, ctx));
|
||||
return 0; // NO main_quit here! vfs_setup may be asynchronous.
|
||||
}
|
||||
|
||||
log_info("Girls are now preparing, please wait warmly...");
|
||||
|
||||
free_cli_action(&ctx->cli);
|
||||
vfs_setup(CALLCHAIN(main_post_vfsinit, ctx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void main_post_vfsinit(CallChainResult ccr) {
|
||||
MainContext *ctx = ccr.ctx;
|
||||
|
||||
if(ctx->headless) {
|
||||
env_set("SDL_AUDIODRIVER", "dummy", true);
|
||||
env_set("SDL_VIDEODRIVER", "dummy", true);
|
||||
env_set("TAISEI_AUDIO_BACKEND", "null", true);
|
||||
|
@ -224,12 +230,7 @@ int main(int argc, char **argv) {
|
|||
init_log_file();
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
env_set("TAISEI_NOASYNC", true, true);
|
||||
env_set("TAISEI_NOPRELOAD", true, true);
|
||||
#endif
|
||||
|
||||
log_info("%s %s", TAISEI_VERSION_FULL, TAISEI_VERSION_BUILD_TYPE);
|
||||
log_version();
|
||||
log_system_specs();
|
||||
log_lib_versions();
|
||||
|
||||
|
@ -238,7 +239,7 @@ int main(int argc, char **argv) {
|
|||
init_sdl();
|
||||
taskmgr_global_init();
|
||||
time_init();
|
||||
init_global(&a);
|
||||
init_global(&ctx->cli);
|
||||
events_init();
|
||||
video_init();
|
||||
init_resources();
|
||||
|
@ -254,64 +255,120 @@ int main(int argc, char **argv) {
|
|||
|
||||
log_info("Initialization complete");
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
atexit(taisei_shutdown);
|
||||
#endif
|
||||
|
||||
if(a.type == CLI_PlayReplay || a.type == CLI_VerifyReplay) {
|
||||
replay_play(&replay, replay_idx);
|
||||
replay_destroy(&replay);
|
||||
return 0;
|
||||
if(ctx->cli.type == CLI_PlayReplay || ctx->cli.type == CLI_VerifyReplay) {
|
||||
main_replay(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
if(a.type == CLI_Credits) {
|
||||
credits_loop();
|
||||
return 0;
|
||||
if(ctx->cli.type == CLI_Credits) {
|
||||
credits_enter(NO_CALLCHAIN);
|
||||
eventloop_run();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
log_warn("Compiled with DEBUG flag!");
|
||||
|
||||
if(a.type == CLI_SelectStage) {
|
||||
log_info("Entering stage skip mode: Stage %X", a.stageid);
|
||||
StageInfo* stg = stage_get(a.stageid);
|
||||
assert(stg); // properly checked before this
|
||||
|
||||
global.diff = stg->difficulty;
|
||||
global.is_practice_mode = (stg->type != STAGE_EXTRA);
|
||||
|
||||
if(a.diff) {
|
||||
global.diff = a.diff;
|
||||
log_info("Setting difficulty to %s", difficulty_name(global.diff));
|
||||
} else if(!global.diff) {
|
||||
global.diff = D_Easy;
|
||||
}
|
||||
|
||||
log_info("Entering %s", stg->title);
|
||||
|
||||
do {
|
||||
global.replay_stage = NULL;
|
||||
replay_init(&global.replay);
|
||||
global.gameover = 0;
|
||||
player_init(&global.plr);
|
||||
|
||||
if(a.plrmode) {
|
||||
global.plr.mode = a.plrmode;
|
||||
}
|
||||
|
||||
stage_loop(stg);
|
||||
|
||||
if(global.gameover == GAMEOVER_RESTART) {
|
||||
replay_destroy(&global.replay);
|
||||
}
|
||||
} while(global.gameover == GAMEOVER_RESTART);
|
||||
|
||||
ask_save_replay();
|
||||
return 0;
|
||||
if(ctx->cli.type == CLI_SelectStage) {
|
||||
main_singlestg(ctx);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
MenuData menu;
|
||||
create_main_menu(&menu);
|
||||
menu_loop(&menu);
|
||||
|
||||
return 0;
|
||||
enter_menu(create_main_menu(), NO_CALLCHAIN);
|
||||
eventloop_run();
|
||||
}
|
||||
|
||||
typedef struct SingleStageContext {
|
||||
PlayerMode *plrmode;
|
||||
StageInfo *stg;
|
||||
} SingleStageContext;
|
||||
|
||||
static void main_singlestg_begin_game(CallChainResult ccr);
|
||||
static void main_singlestg_end_game(CallChainResult ccr);
|
||||
static void main_singlestg_cleanup(CallChainResult ccr);
|
||||
|
||||
static void main_singlestg_begin_game(CallChainResult ccr) {
|
||||
SingleStageContext *ctx = ccr.ctx;
|
||||
|
||||
global.replay_stage = NULL;
|
||||
replay_init(&global.replay);
|
||||
global.gameover = 0;
|
||||
player_init(&global.plr);
|
||||
|
||||
if(ctx->plrmode) {
|
||||
global.plr.mode = ctx->plrmode;
|
||||
}
|
||||
|
||||
stage_enter(ctx->stg, CALLCHAIN(main_singlestg_end_game, ctx));
|
||||
}
|
||||
|
||||
static void main_singlestg_end_game(CallChainResult ccr) {
|
||||
if(global.gameover == GAMEOVER_RESTART) {
|
||||
replay_destroy(&global.replay);
|
||||
main_singlestg_begin_game(ccr);
|
||||
} else {
|
||||
ask_save_replay(CALLCHAIN(main_singlestg_cleanup, ccr.ctx));
|
||||
}
|
||||
}
|
||||
|
||||
static void main_singlestg_cleanup(CallChainResult ccr) {
|
||||
replay_destroy(&global.replay);
|
||||
free(ccr.ctx);
|
||||
}
|
||||
|
||||
static void main_singlestg(MainContext *mctx) {
|
||||
CLIAction *a = &mctx->cli;
|
||||
log_info("Entering stage skip mode: Stage %X", a->stageid);
|
||||
|
||||
StageInfo* stg = stage_get(a->stageid);
|
||||
assert(stg); // properly checked before this
|
||||
|
||||
SingleStageContext *ctx = calloc(1, sizeof(*ctx));
|
||||
ctx->plrmode = a->plrmode;
|
||||
ctx->stg = stg;
|
||||
|
||||
global.diff = stg->difficulty;
|
||||
global.is_practice_mode = (stg->type != STAGE_EXTRA);
|
||||
|
||||
if(a->diff) {
|
||||
global.diff = a->diff;
|
||||
log_info("Setting difficulty to %s", difficulty_name(global.diff));
|
||||
} else if(!global.diff) {
|
||||
global.diff = D_Easy;
|
||||
}
|
||||
|
||||
log_info("Entering %s", stg->title);
|
||||
|
||||
main_singlestg_begin_game(CALLCHAIN_RESULT(ctx, NULL));
|
||||
eventloop_run();
|
||||
}
|
||||
|
||||
static void main_replay(MainContext *mctx) {
|
||||
replay_play(&mctx->replay, mctx->replay_idx, NO_CALLCHAIN);
|
||||
replay_destroy(&mctx->replay); // replay_play makes a copy
|
||||
eventloop_run();
|
||||
}
|
||||
|
||||
static void main_vfstree(CallChainResult ccr) {
|
||||
MainContext *mctx = ccr.ctx;
|
||||
SDL_RWops *rwops = SDL_RWFromFP(stdout, false);
|
||||
int status = 0;
|
||||
|
||||
if(!rwops) {
|
||||
log_fatal("SDL_RWFromFP() failed: %s", SDL_GetError());
|
||||
log_sdl_error(LOG_ERROR, "SDL_RWFromFP");
|
||||
status = 1;
|
||||
} else if(!vfs_print_tree(rwops, mctx->cli.filename)) {
|
||||
log_error("VFS error: %s", vfs_get_error());
|
||||
status = 2;
|
||||
}
|
||||
|
||||
SDL_RWclose(rwops);
|
||||
vfs_shutdown();
|
||||
main_quit(mctx, status);
|
||||
}
|
||||
|
|
|
@ -15,29 +15,14 @@
|
|||
#include "global.h"
|
||||
#include "video.h"
|
||||
|
||||
static void set_player(MenuData *m, void *p) {
|
||||
#define SELECTED_SUBSHOT(m) ((intptr_t)PLR_SHOT_A + (intptr_t)(m)->context)
|
||||
|
||||
static void set_player_mode(MenuData *m, void *p) {
|
||||
progress.game_settings.character = (CharacterID)(uintptr_t)p;
|
||||
}
|
||||
|
||||
static void set_shotmode(MenuData *m, void *p) {
|
||||
progress.game_settings.shotmode = (ShotModeID)(uintptr_t)p;
|
||||
}
|
||||
|
||||
static void create_shottype_menu(MenuData *m) {
|
||||
create_menu(m);
|
||||
m->transition = NULL;
|
||||
|
||||
for(uintptr_t i = 0; i < NUM_SHOT_MODES_PER_CHARACTER; ++i) {
|
||||
add_menu_entry(m, NULL, set_shotmode, (void*)i);
|
||||
|
||||
if(i == progress.game_settings.shotmode) {
|
||||
m->cursor = i;
|
||||
}
|
||||
}
|
||||
progress.game_settings.shotmode = SELECTED_SUBSHOT(m);
|
||||
}
|
||||
|
||||
static void char_menu_input(MenuData*);
|
||||
static void free_char_menu(MenuData*);
|
||||
|
||||
static void update_char_menu(MenuData *menu) {
|
||||
for(int i = 0; i < menu->ecount; i++) {
|
||||
|
@ -45,28 +30,28 @@ static void update_char_menu(MenuData *menu) {
|
|||
}
|
||||
}
|
||||
|
||||
void create_char_menu(MenuData *m) {
|
||||
create_menu(m);
|
||||
MenuData* create_char_menu(void) {
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->input = char_menu_input;
|
||||
m->draw = draw_char_menu;
|
||||
m->logic = update_char_menu;
|
||||
m->end = free_char_menu;
|
||||
m->transition = TransMenuDark;
|
||||
m->flags = MF_Abortable | MF_Transient;
|
||||
m->context = malloc(sizeof(MenuData));
|
||||
create_shottype_menu(m->context);
|
||||
m->flags = MF_Abortable;
|
||||
m->context = (void*)(intptr_t)progress.game_settings.shotmode;
|
||||
|
||||
for(uintptr_t i = 0; i < NUM_CHARACTERS; ++i) {
|
||||
add_menu_entry(m, NULL, set_player, (void*)i)->transition = TransFadeBlack;
|
||||
add_menu_entry(m, NULL, set_player_mode, (void*)i)->transition = TransFadeBlack;
|
||||
|
||||
if(i == progress.game_settings.character) {
|
||||
m->cursor = i;
|
||||
}
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
void draw_char_menu(MenuData *menu) {
|
||||
MenuData *mod = ((MenuData *)menu->context);
|
||||
CullFaceMode cull_saved = r_cull_current();
|
||||
|
||||
draw_options_menu_bg(menu);
|
||||
|
@ -137,11 +122,13 @@ void draw_char_menu(MenuData *menu) {
|
|||
r_mat_push();
|
||||
r_mat_translate(SCREEN_W/4*3, SCREEN_H/3, 0);
|
||||
|
||||
for(int i = 0; i < mod->ecount; i++) {
|
||||
PlayerMode *mode = plrmode_find(current_char, (ShotModeID)(uintptr_t)mod->entries[i].arg);
|
||||
assert(mode != NULL);
|
||||
ShotModeID current_subshot = SELECTED_SUBSHOT(menu);
|
||||
|
||||
if(mod->cursor == i) {
|
||||
for(ShotModeID shot = PLR_SHOT_A; shot < NUM_SHOT_MODES_PER_CHARACTER; shot++) {
|
||||
PlayerMode *mode = plrmode_find(current_char, shot);
|
||||
assume(mode != NULL);
|
||||
|
||||
if(shot == current_subshot) {
|
||||
r_color4(0.9, 0.6, 0.2, 1);
|
||||
} else {
|
||||
r_color4(1, 1, 1, 1);
|
||||
|
@ -149,7 +136,7 @@ void draw_char_menu(MenuData *menu) {
|
|||
|
||||
text_draw(mode->name, &(TextParams) {
|
||||
.align = ALIGN_CENTER,
|
||||
.pos = { 0, 200+40*i },
|
||||
.pos = { 0, 200 + 40 * (shot - PLR_SHOT_A) },
|
||||
.shader = "text_default",
|
||||
});
|
||||
}
|
||||
|
@ -181,7 +168,6 @@ void draw_char_menu(MenuData *menu) {
|
|||
|
||||
static bool char_menu_input_handler(SDL_Event *event, void *arg) {
|
||||
MenuData *menu = arg;
|
||||
MenuData *mod = menu->context;
|
||||
TaiseiEvent type = TAISEI_EVENT(event->type);
|
||||
|
||||
if(type == TE_MENU_CURSOR_RIGHT) {
|
||||
|
@ -192,27 +178,24 @@ static bool char_menu_input_handler(SDL_Event *event, void *arg) {
|
|||
menu->cursor--;
|
||||
} else if(type == TE_MENU_CURSOR_DOWN) {
|
||||
play_ui_sound("generic_shot");
|
||||
mod->cursor++;
|
||||
menu->context = (void*)(SELECTED_SUBSHOT(menu) + 1);
|
||||
} else if(type == TE_MENU_CURSOR_UP) {
|
||||
play_ui_sound("generic_shot");
|
||||
mod->cursor--;
|
||||
menu->context = (void*)(SELECTED_SUBSHOT(menu) - 1);
|
||||
} else if(type == TE_MENU_ACCEPT) {
|
||||
play_ui_sound("shot_special1");
|
||||
mod->selected = mod->cursor;
|
||||
close_menu(mod);
|
||||
menu->selected = menu->cursor;
|
||||
close_menu(menu);
|
||||
|
||||
// XXX: This needs a better fix
|
||||
set_shotmode(mod, mod->entries[mod->selected].arg);
|
||||
} else if(type == TE_MENU_ABORT) {
|
||||
play_ui_sound("hit");
|
||||
close_menu(menu);
|
||||
close_menu(mod);
|
||||
}
|
||||
|
||||
menu->cursor = (menu->cursor % menu->ecount) + menu->ecount*(menu->cursor < 0);
|
||||
mod->cursor = (mod->cursor % mod->ecount) + mod->ecount*(mod->cursor < 0);
|
||||
|
||||
intptr_t ss = SELECTED_SUBSHOT(menu);
|
||||
ss = (ss % NUM_SHOT_MODES_PER_CHARACTER) + NUM_SHOT_MODES_PER_CHARACTER * (ss < 0);
|
||||
menu->context = (void*)ss;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -223,9 +206,3 @@ static void char_menu_input(MenuData *menu) {
|
|||
{ NULL }
|
||||
}, EFLAG_MENU);
|
||||
}
|
||||
|
||||
static void free_char_menu(MenuData *menu) {
|
||||
MenuData *mod = menu->context;
|
||||
destroy_menu(mod);
|
||||
free(mod);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
#include "menu.h"
|
||||
|
||||
void create_char_menu(MenuData *m);
|
||||
MenuData* create_char_menu(void);
|
||||
void draw_char_menu(MenuData *menu);
|
||||
|
||||
#endif // IGUARD_menu_charselect_h
|
||||
|
|
|
@ -19,92 +19,159 @@
|
|||
#include "mainmenu.h"
|
||||
#include "video.h"
|
||||
|
||||
typedef struct StartGameContext {
|
||||
StageInfo *restart_stage;
|
||||
StageInfo *current_stage;
|
||||
MenuData *diff_menu;
|
||||
MenuData *char_menu;
|
||||
Difficulty difficulty;
|
||||
} StartGameContext;
|
||||
|
||||
static void start_game_do_pick_character(CallChainResult ccr);
|
||||
static void start_game_do_enter_stage(CallChainResult ccr);
|
||||
static void start_game_do_leave_stage(CallChainResult ccr);
|
||||
static void start_game_do_show_ending(CallChainResult ccr);
|
||||
static void start_game_do_show_credits(CallChainResult ccr);
|
||||
static void start_game_do_cleanup(CallChainResult ccr);
|
||||
|
||||
static void start_game_internal(MenuData *menu, StageInfo *info, bool difficulty_menu) {
|
||||
MenuData m;
|
||||
Difficulty stagediff;
|
||||
bool restart;
|
||||
StartGameContext *ctx = calloc(1, sizeof(*ctx));
|
||||
|
||||
player_init(&global.plr);
|
||||
if(info == NULL) {
|
||||
global.is_practice_mode = false;
|
||||
ctx->current_stage = stages;
|
||||
ctx->restart_stage = stages;
|
||||
} else {
|
||||
global.is_practice_mode = (info->type != STAGE_EXTRA);
|
||||
ctx->current_stage = info;
|
||||
ctx->restart_stage = info;
|
||||
}
|
||||
|
||||
do {
|
||||
restart = false;
|
||||
stagediff = info ? info->difficulty : D_Any;
|
||||
Difficulty stagediff = info ? info->difficulty : D_Any;
|
||||
|
||||
if(stagediff == D_Any) {
|
||||
if(difficulty_menu) {
|
||||
create_difficulty_menu(&m);
|
||||
if(menu_loop(&m) == -1) {
|
||||
return;
|
||||
}
|
||||
CallChain cc_pick_character = CALLCHAIN(start_game_do_pick_character, ctx);
|
||||
|
||||
global.diff = progress.game_settings.difficulty;
|
||||
} else {
|
||||
// assume global.diff is set up beforehand
|
||||
}
|
||||
if(stagediff == D_Any) {
|
||||
if(difficulty_menu) {
|
||||
ctx->diff_menu = create_difficulty_menu();
|
||||
enter_menu(ctx->diff_menu, cc_pick_character);
|
||||
} else {
|
||||
global.diff = stagediff;
|
||||
ctx->difficulty = progress.game_settings.difficulty;
|
||||
run_call_chain(&cc_pick_character, NULL);
|
||||
}
|
||||
} else {
|
||||
ctx->difficulty = stagediff;
|
||||
run_call_chain(&cc_pick_character, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void start_game_do_pick_character(CallChainResult ccr) {
|
||||
StartGameContext *ctx = ccr.ctx;
|
||||
MenuData *prev_menu = ccr.result;
|
||||
|
||||
if(prev_menu) {
|
||||
if(prev_menu->state == MS_Dead) {
|
||||
start_game_do_cleanup(ccr);
|
||||
return;
|
||||
}
|
||||
|
||||
create_char_menu(&m);
|
||||
if(menu_loop(&m) == -1) {
|
||||
if(stagediff != D_Any || !difficulty_menu) {
|
||||
return;
|
||||
}
|
||||
// came here from the difficulty menu - update our setting
|
||||
ctx->difficulty = progress.game_settings.difficulty;
|
||||
}
|
||||
|
||||
restart = true;
|
||||
}
|
||||
} while(restart);
|
||||
assert(ctx->char_menu == NULL);
|
||||
ctx->char_menu = create_char_menu();
|
||||
enter_menu(ctx->char_menu, CALLCHAIN(start_game_do_enter_stage, ctx));
|
||||
}
|
||||
|
||||
static void reset_game(StartGameContext *ctx) {
|
||||
ctx->current_stage = ctx->restart_stage;
|
||||
|
||||
global.gameover = GAMEOVER_NONE;
|
||||
global.replay_stage = NULL;
|
||||
replay_destroy(&global.replay);
|
||||
replay_init(&global.replay);
|
||||
player_init(&global.plr);
|
||||
global.plr.mode = plrmode_find(
|
||||
progress.game_settings.character,
|
||||
progress.game_settings.shotmode
|
||||
);
|
||||
global.diff = ctx->difficulty;
|
||||
|
||||
assert(global.plr.mode != NULL);
|
||||
}
|
||||
|
||||
global.replay_stage = NULL;
|
||||
replay_init(&global.replay);
|
||||
PlayerMode *mode = global.plr.mode;
|
||||
static void kill_aux_menus(StartGameContext *ctx) {
|
||||
kill_menu(ctx->char_menu);
|
||||
kill_menu(ctx->diff_menu);
|
||||
ctx->char_menu = ctx->diff_menu = NULL;
|
||||
}
|
||||
|
||||
do {
|
||||
restart = false;
|
||||
static void start_game_do_enter_stage(CallChainResult ccr) {
|
||||
StartGameContext *ctx = ccr.ctx;
|
||||
MenuData *prev_menu = ccr.result;
|
||||
|
||||
if(info) {
|
||||
global.is_practice_mode = (info->type != STAGE_EXTRA);
|
||||
stage_loop(info);
|
||||
} else {
|
||||
global.is_practice_mode = false;
|
||||
for(StageInfo *s = stages; s->type == STAGE_STORY; ++s) {
|
||||
stage_loop(s);
|
||||
}
|
||||
}
|
||||
|
||||
if(global.gameover == GAMEOVER_RESTART) {
|
||||
replay_destroy(&global.replay);
|
||||
replay_init(&global.replay);
|
||||
global.gameover = 0;
|
||||
player_init(&global.plr);
|
||||
global.plr.mode = mode;
|
||||
|
||||
restart = true;
|
||||
}
|
||||
} while(restart);
|
||||
|
||||
free_resources(false);
|
||||
ask_save_replay();
|
||||
|
||||
global.replay_stage = NULL;
|
||||
|
||||
if(global.gameover == GAMEOVER_WIN && !info) {
|
||||
ending_loop();
|
||||
credits_loop();
|
||||
free_resources(false);
|
||||
if(prev_menu && prev_menu->state == MS_Dead) {
|
||||
assert(prev_menu == ctx->char_menu);
|
||||
ctx->char_menu = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
start_bgm("menu");
|
||||
kill_aux_menus(ctx);
|
||||
reset_game(ctx);
|
||||
stage_enter(ctx->current_stage, CALLCHAIN(start_game_do_leave_stage, ctx));
|
||||
}
|
||||
|
||||
static void start_game_do_leave_stage(CallChainResult ccr) {
|
||||
StartGameContext *ctx = ccr.ctx;
|
||||
|
||||
if(global.gameover == GAMEOVER_RESTART) {
|
||||
reset_game(ctx);
|
||||
stage_enter(ctx->current_stage, CALLCHAIN(start_game_do_leave_stage, ctx));
|
||||
return;
|
||||
}
|
||||
|
||||
if(ctx->current_stage->type == STAGE_STORY && !global.is_practice_mode) {
|
||||
++ctx->current_stage;
|
||||
|
||||
if(ctx->current_stage->type == STAGE_STORY) {
|
||||
stage_enter(ctx->current_stage, CALLCHAIN(start_game_do_leave_stage, ctx));
|
||||
} else {
|
||||
CallChain cc;
|
||||
|
||||
if(global.gameover == GAMEOVER_WIN) {
|
||||
ending_preload();
|
||||
credits_preload();
|
||||
cc = CALLCHAIN(start_game_do_show_ending, ctx);
|
||||
} else {
|
||||
cc = CALLCHAIN(start_game_do_cleanup, ctx);
|
||||
}
|
||||
|
||||
ask_save_replay(cc);
|
||||
}
|
||||
} else {
|
||||
ask_save_replay(CALLCHAIN(start_game_do_cleanup, ctx));
|
||||
}
|
||||
}
|
||||
|
||||
static void start_game_do_show_ending(CallChainResult ccr) {
|
||||
ending_enter(CALLCHAIN(start_game_do_show_credits, ccr.ctx));
|
||||
}
|
||||
|
||||
static void start_game_do_show_credits(CallChainResult ccr) {
|
||||
credits_enter(CALLCHAIN(start_game_do_cleanup, ccr.ctx));
|
||||
}
|
||||
|
||||
static void start_game_do_cleanup(CallChainResult ccr) {
|
||||
StartGameContext *ctx = ccr.ctx;
|
||||
kill_aux_menus(ctx);
|
||||
free(ctx);
|
||||
free_resources(false);
|
||||
global.replay_stage = NULL;
|
||||
global.gameover = GAMEOVER_NONE;
|
||||
replay_destroy(&global.replay);
|
||||
main_menu_update_practice_menus();
|
||||
global.gameover = 0;
|
||||
start_bgm("menu");
|
||||
}
|
||||
|
||||
void start_game(MenuData *m, void *arg) {
|
||||
|
@ -126,7 +193,7 @@ void draw_menu_selector(float x, float y, float w, float h, float t) {
|
|||
r_mat_pop();
|
||||
}
|
||||
|
||||
void draw_menu_title(MenuData *m, char *title) {
|
||||
void draw_menu_title(MenuData *m, const char *title) {
|
||||
text_draw(title, &(TextParams) {
|
||||
.pos = { (text_width(get_font("big"), title, 0) + 10) * (1.0 - menu_fade(m)), 30 },
|
||||
.align = ALIGN_RIGHT,
|
||||
|
@ -200,6 +267,6 @@ void animate_menu_list(MenuData *m) {
|
|||
animate_menu_list_entries(m);
|
||||
}
|
||||
|
||||
void menu_commonaction_close(MenuData *menu, void *arg) {
|
||||
void menu_action_close(MenuData *menu, void *arg) {
|
||||
menu->state = MS_Dead;
|
||||
}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
void start_game(MenuData *m, void *arg);
|
||||
void start_game_no_difficulty_menu(MenuData *m, void *arg);
|
||||
void draw_menu_selector(float x, float y, float w, float h, float t);
|
||||
void draw_menu_title(MenuData *m, char *title);
|
||||
void draw_menu_title(MenuData *m, const char *title);
|
||||
void draw_menu_list(MenuData *m, float x, float y, void (*draw)(void*, int, int));
|
||||
void animate_menu_list(MenuData *m);
|
||||
void animate_menu_list_entries(MenuData *m);
|
||||
void animate_menu_list_entry(MenuData *m, int i);
|
||||
void menu_commonaction_close(MenuData *menu, void *arg);
|
||||
void menu_action_close(MenuData *menu, void *arg);
|
||||
|
||||
#endif // IGUARD_menu_common_h
|
||||
|
|
|
@ -32,12 +32,13 @@ static void update_difficulty_menu(MenuData *menu) {
|
|||
color_approach(&diff_color, difficulty_color(menu->cursor + D_Easy), 0.1);
|
||||
}
|
||||
|
||||
void create_difficulty_menu(MenuData *m) {
|
||||
create_menu(m);
|
||||
MenuData* create_difficulty_menu(void) {
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->draw = draw_difficulty_menu;
|
||||
m->logic = update_difficulty_menu;
|
||||
m->transition = TransMenuDark;
|
||||
m->flags = MF_Transient | MF_Abortable;
|
||||
m->flags = MF_Abortable;
|
||||
|
||||
add_menu_entry(m, "“All those bullets confuse me!”\nYou will be stuck here forever", set_difficulty, (void *)D_Easy);
|
||||
add_menu_entry(m, "“So it's not just about shooting stuff?”\nSomewhat challenging", set_difficulty, (void *)D_Normal);
|
||||
|
@ -52,6 +53,8 @@ void create_difficulty_menu(MenuData *m) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
void draw_difficulty_menu(MenuData *menu) {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
#include "menu.h"
|
||||
|
||||
void create_difficulty_menu(MenuData *menu);
|
||||
MenuData* create_difficulty_menu(void);
|
||||
void draw_difficulty_menu(MenuData *m);
|
||||
|
||||
#endif // IGUARD_menu_difficultyselect_h
|
||||
|
|
|
@ -23,8 +23,9 @@ static void give_up(MenuData *m, void *arg) {
|
|||
global.gameover = (MAX_CONTINUES - global.plr.continues_used)? GAMEOVER_ABORT : GAMEOVER_DEFEAT;
|
||||
}
|
||||
|
||||
void create_gameover_menu(MenuData *m) {
|
||||
create_menu(m);
|
||||
MenuData* create_gameover_menu(void) {
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->draw = draw_ingame_menu;
|
||||
m->logic = update_ingame_menu;
|
||||
m->flags = MF_Transient | MF_AlwaysProcessInput;
|
||||
|
@ -50,4 +51,5 @@ void create_gameover_menu(MenuData *m) {
|
|||
}
|
||||
|
||||
set_transition(TransEmpty, 0, m->transition_out_time);
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
|
||||
#include "menu.h"
|
||||
|
||||
void create_gameover_menu(MenuData *);
|
||||
MenuData* create_gameover_menu(void);
|
||||
|
||||
#endif // IGUARD_menu_gameovermenu_h
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
|
||||
static void return_to_title(MenuData *m, void *arg) {
|
||||
global.gameover = GAMEOVER_ABORT;
|
||||
menu_commonaction_close(m, arg);
|
||||
menu_action_close(m, arg);
|
||||
}
|
||||
|
||||
void restart_game(MenuData *m, void *arg) {
|
||||
global.gameover = GAMEOVER_RESTART;
|
||||
menu_commonaction_close(m, arg);
|
||||
menu_action_close(m, arg);
|
||||
}
|
||||
|
||||
static void ingame_menu_do(MenuData *m, MenuAction action) {
|
||||
|
@ -113,8 +113,9 @@ static void ingame_menu_input(MenuData *m) {
|
|||
}, EFLAG_MENU);
|
||||
}
|
||||
|
||||
void create_ingame_menu(MenuData *m) {
|
||||
create_menu(m);
|
||||
MenuData* create_ingame_menu(void) {
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->draw = draw_ingame_menu;
|
||||
m->logic = update_ingame_menu;
|
||||
m->input = ingame_menu_input;
|
||||
|
@ -122,33 +123,38 @@ void create_ingame_menu(MenuData *m) {
|
|||
m->transition = TransEmpty;
|
||||
m->cursor = 1;
|
||||
m->context = "Game Paused";
|
||||
add_menu_entry(m, "Options", enter_options, NULL)->transition = TransMenuDark;
|
||||
add_menu_entry(m, "Return to Game", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Options", menu_action_enter_options, NULL)->transition = TransMenuDark;
|
||||
add_menu_entry(m, "Return to Game", menu_action_close, NULL);
|
||||
add_menu_entry(m, "Restart the Game", restart_game, NULL)->transition = TransFadeBlack;
|
||||
add_menu_entry(m, "Stop the Game", return_to_title, NULL)->transition = TransFadeBlack;
|
||||
set_transition(TransEmpty, 0, m->transition_out_time);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
static void skip_stage(MenuData *m, void *arg) {
|
||||
global.gameover = GAMEOVER_WIN;
|
||||
menu_commonaction_close(m, arg);
|
||||
menu_action_close(m, arg);
|
||||
}
|
||||
|
||||
void create_ingame_menu_replay(MenuData *m) {
|
||||
create_menu(m);
|
||||
MenuData* create_ingame_menu_replay(void) {
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->draw = draw_ingame_menu;
|
||||
m->logic = update_ingame_menu;
|
||||
m->flags = MF_Abortable | MF_AlwaysProcessInput;
|
||||
m->transition = TransEmpty;
|
||||
m->cursor = 1;
|
||||
m->context = "Replay Paused";
|
||||
add_menu_entry(m, "Options", enter_options, NULL)->transition = TransMenuDark;
|
||||
add_menu_entry(m, "Continue Watching", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Options", menu_action_enter_options, NULL)->transition = TransMenuDark;
|
||||
add_menu_entry(m, "Continue Watching", menu_action_close, NULL);
|
||||
add_menu_entry(m, "Restart the Stage", restart_game, NULL)->transition = TransFadeBlack;
|
||||
add_menu_entry(m, "Skip the Stage", skip_stage, NULL)->transition = TransFadeBlack;
|
||||
add_menu_entry(m, "Stop Watching", return_to_title, NULL)->transition = TransFadeBlack;
|
||||
m->cursor = 1;
|
||||
set_transition(TransEmpty, 0, m->transition_out_time);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
void draw_ingame_menu_bg(MenuData *menu, float f) {
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
void draw_ingame_menu_bg(MenuData *menu, float f);
|
||||
void draw_ingame_menu(MenuData *menu);
|
||||
|
||||
void create_ingame_menu(MenuData *menu);
|
||||
void create_ingame_menu_replay(MenuData *m);
|
||||
MenuData* create_ingame_menu(void);
|
||||
MenuData* create_ingame_menu_replay(void);
|
||||
|
||||
void update_ingame_menu(MenuData *menu);
|
||||
|
||||
|
|
|
@ -36,14 +36,14 @@ void main_menu_update_practice_menus(void) {
|
|||
StageProgress *p = stage_get_progress_from_info(stg, D_Any, false);
|
||||
|
||||
if(p && p->unlocked) {
|
||||
spell_practice_entry->action = enter_spellpractice;
|
||||
spell_practice_entry->action = menu_action_enter_spellpractice;
|
||||
}
|
||||
} else if(stg->type == STAGE_STORY) {
|
||||
for(Difficulty d = D_Easy; d <= D_Lunatic; ++d) {
|
||||
StageProgress *p = stage_get_progress_from_info(stg, d, false);
|
||||
|
||||
if(p && p->unlocked) {
|
||||
stage_practice_entry->action = enter_stagepractice;
|
||||
stage_practice_entry->action = menu_action_enter_stagepractice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ static void update_main_menu(MenuData *menu) {
|
|||
}
|
||||
}
|
||||
|
||||
attr_unused
|
||||
static bool main_menu_input_handler(SDL_Event *event, void *arg) {
|
||||
MenuData *m = arg;
|
||||
TaiseiEvent te = TAISEI_EVENT(event->type);
|
||||
|
@ -85,6 +86,7 @@ static bool main_menu_input_handler(SDL_Event *event, void *arg) {
|
|||
return menu_input_handler(event, arg);
|
||||
}
|
||||
|
||||
attr_unused
|
||||
static void main_menu_input(MenuData *m) {
|
||||
events_poll((EventHandler[]){
|
||||
{ .proc = main_menu_input_handler, .arg = m },
|
||||
|
@ -92,27 +94,34 @@ static void main_menu_input(MenuData *m) {
|
|||
}, EFLAG_MENU);
|
||||
}
|
||||
|
||||
void create_main_menu(MenuData *m) {
|
||||
create_menu(m);
|
||||
MenuData* create_main_menu(void) {
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->begin = begin_main_menu;
|
||||
m->draw = draw_main_menu;
|
||||
m->logic = update_main_menu;
|
||||
m->begin = begin_main_menu;
|
||||
m->input = main_menu_input;
|
||||
|
||||
add_menu_entry(m, "Start Story", start_game, NULL);
|
||||
add_menu_entry(m, "Start Extra", NULL, NULL);
|
||||
add_menu_entry(m, "Stage Practice", enter_stagepractice, NULL);
|
||||
add_menu_entry(m, "Spell Practice", enter_spellpractice, NULL);
|
||||
add_menu_entry(m, "Stage Practice", menu_action_enter_stagepractice, NULL);
|
||||
add_menu_entry(m, "Spell Practice", menu_action_enter_spellpractice, NULL);
|
||||
#ifdef DEBUG
|
||||
add_menu_entry(m, "Select Stage", enter_stagemenu, NULL);
|
||||
add_menu_entry(m, "Select Stage", menu_action_enter_stagemenu, NULL);
|
||||
#endif
|
||||
add_menu_entry(m, "Replays", menu_action_enter_replayview, NULL);
|
||||
add_menu_entry(m, "Options", menu_action_enter_options, NULL);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
add_menu_entry(m, "Quit", menu_action_close, NULL)->transition = TransFadeBlack;
|
||||
m->input = main_menu_input;
|
||||
#endif
|
||||
add_menu_entry(m, "Replays", enter_replayview, NULL);
|
||||
add_menu_entry(m, "Options", enter_options, NULL);
|
||||
add_menu_entry(m, "Quit", menu_commonaction_close, NULL)->transition = TransFadeBlack;
|
||||
|
||||
stage_practice_entry = m->entries + 2;
|
||||
spell_practice_entry = m->entries + 3;
|
||||
main_menu_update_practice_menus();
|
||||
|
||||
start_bgm("menu");
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
void draw_main_menu_bg(MenuData* menu) {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
#include "menu.h"
|
||||
|
||||
void create_main_menu(MenuData *m);
|
||||
MenuData* create_main_menu(void);
|
||||
void draw_main_menu_bg(MenuData *m);
|
||||
void draw_main_menu(MenuData *m);
|
||||
void main_menu_update_practice_menus(void);
|
||||
|
|
|
@ -30,26 +30,42 @@ void add_menu_separator(MenuData *menu) {
|
|||
memset(menu->entries + menu->ecount - 1, 0, sizeof(MenuEntry));
|
||||
}
|
||||
|
||||
void destroy_menu(MenuData *menu) {
|
||||
void free_menu(MenuData *menu) {
|
||||
if(menu == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < menu->ecount; i++) {
|
||||
free(menu->entries[i].name);
|
||||
}
|
||||
|
||||
free(menu->entries);
|
||||
free(menu);
|
||||
}
|
||||
|
||||
void create_menu(MenuData *menu) {
|
||||
memset(menu, 0, sizeof(MenuData));
|
||||
|
||||
MenuData* alloc_menu(void) {
|
||||
MenuData *menu = calloc(1, sizeof(*menu));
|
||||
menu->selected = -1;
|
||||
menu->transition = TransMenu; // TransFadeBlack;
|
||||
menu->transition_in_time = FADE_TIME;
|
||||
menu->transition_out_time = FADE_TIME;
|
||||
menu->fade = 1.0;
|
||||
menu->input = menu_input;
|
||||
return menu;
|
||||
}
|
||||
|
||||
void kill_menu(MenuData *menu) {
|
||||
if(menu != NULL) {
|
||||
menu->state = MS_Dead;
|
||||
// nani?!
|
||||
}
|
||||
}
|
||||
|
||||
static void close_menu_finish(MenuData *menu) {
|
||||
// This may happen with MF_AlwaysProcessInput menus, so make absolutely sure we
|
||||
// never run the call chain with menu->state == MS_Dead more than once.
|
||||
bool was_dead = (menu->state == MS_Dead);
|
||||
|
||||
menu->state = MS_Dead;
|
||||
|
||||
if(menu->selected != -1 && menu->entries[menu->selected].action != NULL) {
|
||||
|
@ -59,6 +75,10 @@ static void close_menu_finish(MenuData *menu) {
|
|||
|
||||
menu->entries[menu->selected].action(menu, menu->entries[menu->selected].arg);
|
||||
}
|
||||
|
||||
if(!was_dead) {
|
||||
run_call_chain(&menu->cc, menu);
|
||||
}
|
||||
}
|
||||
|
||||
void close_menu(MenuData *menu) {
|
||||
|
@ -144,9 +164,13 @@ void menu_no_input(MenuData *menu) {
|
|||
events_poll(NULL, 0);
|
||||
}
|
||||
|
||||
static FrameAction menu_logic_frame(void *arg) {
|
||||
static LogicFrameAction menu_logic_frame(void *arg) {
|
||||
MenuData *menu = arg;
|
||||
|
||||
if(menu->state == MS_Dead) {
|
||||
return LFRAME_STOP;
|
||||
}
|
||||
|
||||
if(menu->logic) {
|
||||
menu->logic(menu);
|
||||
}
|
||||
|
@ -162,30 +186,45 @@ static FrameAction menu_logic_frame(void *arg) {
|
|||
|
||||
update_transition();
|
||||
|
||||
return menu->state == MS_Dead ? LFRAME_STOP : LFRAME_WAIT;
|
||||
return LFRAME_WAIT;
|
||||
}
|
||||
|
||||
static FrameAction menu_render_frame(void *arg) {
|
||||
static RenderFrameAction menu_render_frame(void *arg) {
|
||||
MenuData *menu = arg;
|
||||
assert(menu->draw);
|
||||
set_ortho(SCREEN_W, SCREEN_H);
|
||||
menu->draw(menu);
|
||||
draw_transition();
|
||||
return RFRAME_SWAP;
|
||||
}
|
||||
|
||||
int menu_loop(MenuData *menu) {
|
||||
set_ortho(SCREEN_W, SCREEN_H);
|
||||
static void menu_end_loop(void *ctx) {
|
||||
MenuData *menu = ctx;
|
||||
|
||||
if(menu->begin) {
|
||||
menu->begin(menu);
|
||||
if(menu->state != MS_Dead) {
|
||||
// definitely dead now...
|
||||
menu->state = MS_Dead;
|
||||
run_call_chain(&menu->cc, menu);
|
||||
}
|
||||
|
||||
loop_at_fps(menu_logic_frame, menu_render_frame, menu, FPS);
|
||||
|
||||
if(menu->end) {
|
||||
menu->end(menu);
|
||||
}
|
||||
|
||||
destroy_menu(menu);
|
||||
return menu->selected;
|
||||
free_menu(menu);
|
||||
}
|
||||
|
||||
void enter_menu(MenuData *menu, CallChain next) {
|
||||
if(menu == NULL) {
|
||||
run_call_chain(&next, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
menu->cc = next;
|
||||
|
||||
if(menu->begin != NULL) {
|
||||
menu->begin(menu);
|
||||
}
|
||||
|
||||
eventloop_enter(menu, menu_logic_frame, menu_render_frame, menu_end_loop, FPS);
|
||||
}
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
#include "taisei.h"
|
||||
|
||||
#include "transition.h"
|
||||
#include "events.h"
|
||||
#include "eventloop/eventloop.h"
|
||||
|
||||
#define IMENU_BLUR 0.05
|
||||
|
||||
#include "events.h"
|
||||
|
||||
enum {
|
||||
FADE_TIME = 15
|
||||
};
|
||||
|
@ -69,29 +69,33 @@ struct MenuData {
|
|||
|
||||
void *context;
|
||||
|
||||
MenuProc begin;
|
||||
MenuProc draw;
|
||||
MenuProc input;
|
||||
MenuProc logic;
|
||||
MenuProc begin;
|
||||
MenuProc end;
|
||||
|
||||
CallChain cc;
|
||||
};
|
||||
|
||||
MenuData *alloc_menu(void);
|
||||
void free_menu(MenuData *menu);
|
||||
void close_menu(MenuData *menu);
|
||||
void kill_menu(MenuData *menu);
|
||||
|
||||
// You will get a pointer to the MenuData in callchain result.
|
||||
// WARNING: Unless the menu state is MS_Dead at the time your callback is invoked, it may be called again!
|
||||
// This must be accounted for, otherwise demons will literally fly out of your nose.
|
||||
// No joke. Not *might*, they actually *will*. I wouldn't recommend testing that out.
|
||||
void enter_menu(MenuData *menu, CallChain next);
|
||||
|
||||
MenuEntry *add_menu_entry(MenuData *menu, const char *name, MenuAction action, void *arg);
|
||||
MenuEntry *add_menu_entry_f(MenuData *menu, char *name, MenuAction action, void *arg, int flags);
|
||||
|
||||
void add_menu_separator(MenuData *menu);
|
||||
void create_menu(MenuData *menu);
|
||||
void destroy_menu(MenuData *menu);
|
||||
|
||||
void menu_input(MenuData *menu);
|
||||
void menu_no_input(MenuData *menu);
|
||||
|
||||
void close_menu(MenuData *menu);
|
||||
|
||||
void menu_key_action(MenuData *menu, int sym);
|
||||
|
||||
int menu_loop(MenuData *menu);
|
||||
|
||||
float menu_fade(MenuData *menu);
|
||||
|
||||
bool menu_input_handler(SDL_Event *event, void *arg);
|
||||
|
|
|
@ -15,6 +15,45 @@
|
|||
#include "global.h"
|
||||
#include "video.h"
|
||||
|
||||
typedef struct OptionBinding OptionBinding;
|
||||
|
||||
typedef int (*BindingGetter)(OptionBinding*);
|
||||
typedef int (*BindingSetter)(OptionBinding*, int);
|
||||
typedef bool (*BindingDependence)(void);
|
||||
|
||||
typedef enum BindingType {
|
||||
BT_IntValue,
|
||||
BT_KeyBinding,
|
||||
BT_StrValue,
|
||||
BT_Resolution,
|
||||
BT_Scale,
|
||||
BT_GamepadKeyBinding,
|
||||
BT_GamepadAxisBinding,
|
||||
BT_GamepadDevice,
|
||||
} BindingType;
|
||||
|
||||
typedef struct OptionBinding {
|
||||
union {
|
||||
char **values;
|
||||
char *strvalue;
|
||||
};
|
||||
bool displaysingle;
|
||||
int valcount;
|
||||
int valrange_min;
|
||||
int valrange_max;
|
||||
float scale_min;
|
||||
float scale_max;
|
||||
float scale_step;
|
||||
BindingGetter getter;
|
||||
BindingSetter setter;
|
||||
BindingDependence dependence;
|
||||
int selected;
|
||||
int configentry;
|
||||
BindingType type;
|
||||
bool blockinput;
|
||||
int pad;
|
||||
} OptionBinding;
|
||||
|
||||
// --- Menu entry <-> config option binding stuff --- //
|
||||
|
||||
static void bind_init(OptionBinding *bind) {
|
||||
|
@ -241,10 +280,6 @@ static int bind_setprev(OptionBinding *b) {
|
|||
return bind_setvalue(b, s);
|
||||
}
|
||||
|
||||
static void bind_setdependence(OptionBinding *b, BindingDependence dep) {
|
||||
b->dependence = dep;
|
||||
}
|
||||
|
||||
static bool bind_isactive(OptionBinding *b) {
|
||||
if(!b->dependence)
|
||||
return true;
|
||||
|
@ -298,7 +333,7 @@ static int bind_common_intplus1_set(OptionBinding *b, int v) {
|
|||
// --- Binding callbacks for individual options --- //
|
||||
|
||||
static bool bind_resizable_dependence(void) {
|
||||
return !config_get_int(CONFIG_FULLSCREEN);
|
||||
return video_query_capability(VIDEO_CAP_EXTERNAL_RESIZE) == VIDEO_AVAILABLE;
|
||||
}
|
||||
|
||||
static bool bind_bgquality_dependence(void) {
|
||||
|
@ -306,7 +341,11 @@ static bool bind_bgquality_dependence(void) {
|
|||
}
|
||||
|
||||
static bool bind_resolution_dependence(void) {
|
||||
return video_can_change_resolution();
|
||||
return video_query_capability(VIDEO_CAP_CHANGE_RESOLUTION) == VIDEO_AVAILABLE;
|
||||
}
|
||||
|
||||
static bool bind_fullscreen_dependence(void) {
|
||||
return video_query_capability(VIDEO_CAP_FULLSCREEN) == VIDEO_AVAILABLE;
|
||||
}
|
||||
|
||||
static int bind_resolution_set(OptionBinding *b, int v) {
|
||||
|
@ -329,6 +368,11 @@ static int bind_power_get(OptionBinding *b) {
|
|||
|
||||
// --- Creating, destroying, filling the menu --- //
|
||||
|
||||
typedef struct OptionsMenuContext {
|
||||
const char *title;
|
||||
void *data;
|
||||
} OptionsMenuContext;
|
||||
|
||||
static void destroy_options_menu(MenuData *m) {
|
||||
for(int i = 0; i < m->ecount; ++i) {
|
||||
OptionBinding *bind = bind_get(m, i);
|
||||
|
@ -337,7 +381,7 @@ static void destroy_options_menu(MenuData *m) {
|
|||
continue;
|
||||
}
|
||||
|
||||
if(bind->type == BT_Resolution && video_can_change_resolution()) {
|
||||
if(bind->type == BT_Resolution && video_query_capability(VIDEO_CAP_CHANGE_RESOLUTION) == VIDEO_AVAILABLE) {
|
||||
if(bind->selected != -1) {
|
||||
VideoMode *mode = video.modes + bind->selected;
|
||||
|
||||
|
@ -354,35 +398,67 @@ static void destroy_options_menu(MenuData *m) {
|
|||
bind_free(bind);
|
||||
free(bind);
|
||||
}
|
||||
|
||||
if(m->context) {
|
||||
OptionsMenuContext *ctx = m->context;
|
||||
free(ctx->data);
|
||||
free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static void do_nothing(MenuData *menu, void *arg) { }
|
||||
static void update_options_menu(MenuData *menu);
|
||||
void options_menu_input(MenuData*);
|
||||
static void options_menu_input(MenuData*);
|
||||
static void draw_options_menu(MenuData*);
|
||||
|
||||
static void create_options_menu_basic(MenuData *m, char *s) {
|
||||
create_menu(m);
|
||||
#define bind_onoff(b) bind_addvalue(b, "on"); bind_addvalue(b, "off")
|
||||
|
||||
static MenuData* create_options_menu_base(const char *s) {
|
||||
MenuData *m = alloc_menu();
|
||||
m->transition = TransMenuDark;
|
||||
m->flags = MF_Abortable;
|
||||
m->context = s;
|
||||
m->input = options_menu_input;
|
||||
m->draw = draw_options_menu;
|
||||
m->logic = update_options_menu;
|
||||
m->end = destroy_options_menu;
|
||||
|
||||
OptionsMenuContext *ctx = calloc(1, sizeof(OptionsMenuContext));
|
||||
ctx->title = s;
|
||||
m->context = ctx;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
#define bind_onoff(b) bind_addvalue(b, "on"); bind_addvalue(b, "off")
|
||||
static void options_enter_sub(MenuData *parent, MenuData *(*construct)(MenuData*)) {
|
||||
parent->frames = 0;
|
||||
enter_menu(construct(parent), NO_CALLCHAIN);
|
||||
}
|
||||
|
||||
static void options_sub_video(MenuData *parent, void *arg) {
|
||||
MenuData menu, *m;
|
||||
#define DECLARE_ENTER_FUNC(enter, construct) \
|
||||
static void enter(MenuData *parent, void *arg) { \
|
||||
options_enter_sub(parent, construct); \
|
||||
}
|
||||
|
||||
static MenuData* create_options_menu_controls(MenuData *parent);
|
||||
DECLARE_ENTER_FUNC(enter_options_menu_controls, create_options_menu_controls)
|
||||
|
||||
static MenuData* create_options_menu_gamepad(MenuData *parent);
|
||||
DECLARE_ENTER_FUNC(enter_options_menu_gamepad, create_options_menu_gamepad)
|
||||
|
||||
static MenuData* create_options_menu_gamepad_controls(MenuData *parent);
|
||||
DECLARE_ENTER_FUNC(enter_options_menu_gamepad_controls, create_options_menu_gamepad_controls)
|
||||
|
||||
static MenuData* create_options_menu_video(MenuData *parent);
|
||||
DECLARE_ENTER_FUNC(enter_options_menu_video, create_options_menu_video)
|
||||
|
||||
static MenuData* create_options_menu_video(MenuData *parent) {
|
||||
MenuData *m = create_options_menu_base("Video Options");
|
||||
OptionBinding *b;
|
||||
m = &menu;
|
||||
|
||||
create_options_menu_basic(m, "Video Options");
|
||||
|
||||
add_menu_entry(m, "Fullscreen", do_nothing,
|
||||
b = bind_option(CONFIG_FULLSCREEN, bind_common_onoff_get, bind_common_onoff_set)
|
||||
); bind_onoff(b);
|
||||
b->dependence = bind_fullscreen_dependence;
|
||||
|
||||
add_menu_entry(m, "Window size", do_nothing,
|
||||
b = bind_resolution()
|
||||
|
@ -392,7 +468,7 @@ static void options_sub_video(MenuData *parent, void *arg) {
|
|||
add_menu_entry(m, "Resizable window", do_nothing,
|
||||
b = bind_option(CONFIG_VID_RESIZABLE, bind_common_onoff_get, bind_common_onoff_set)
|
||||
); bind_onoff(b);
|
||||
bind_setdependence(b, bind_resizable_dependence);
|
||||
b->dependence = bind_resizable_dependence;
|
||||
|
||||
add_menu_entry(m, "Pause the game when it's not focused", do_nothing,
|
||||
b = bind_option(CONFIG_FOCUS_LOSS_PAUSE, bind_common_onoff_get, bind_common_onoff_set)
|
||||
|
@ -404,7 +480,10 @@ static void options_sub_video(MenuData *parent, void *arg) {
|
|||
b = bind_option(CONFIG_VSYNC, bind_common_onoffplus_get, bind_common_onoffplus_set)
|
||||
); bind_addvalue(b, "on");
|
||||
bind_addvalue(b, "off");
|
||||
|
||||
if(video_query_capability(VIDEO_CAP_VSYNC_ADAPTIVE) != VIDEO_NEVER_AVAILABLE) {
|
||||
bind_addvalue(b, "adaptive");
|
||||
}
|
||||
|
||||
add_menu_entry(m, "Skip frames", do_nothing,
|
||||
b = bind_option(CONFIG_VID_FRAMESKIP, bind_common_intplus1_get, bind_common_intplus1_set)
|
||||
|
@ -446,10 +525,9 @@ static void options_sub_video(MenuData *parent, void *arg) {
|
|||
bind_addvalue(b, "full");
|
||||
|
||||
add_menu_separator(m);
|
||||
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Back", menu_action_close, NULL);
|
||||
|
||||
menu_loop(m);
|
||||
parent->frames = 0;
|
||||
return m;
|
||||
}
|
||||
|
||||
attr_unused
|
||||
|
@ -465,11 +543,8 @@ static bool gamepad_enabled_depencence(void) {
|
|||
return config_get_int(CONFIG_GAMEPAD_ENABLED);
|
||||
}
|
||||
|
||||
static void options_sub_gamepad_controls(MenuData *parent, void *arg) {
|
||||
MenuData menu, *m;
|
||||
m = &menu;
|
||||
|
||||
create_options_menu_basic(m, "Gamepad Controls");
|
||||
static MenuData* create_options_menu_gamepad_controls(MenuData *parent) {
|
||||
MenuData *m = create_options_menu_base("Gamepad Controls");
|
||||
|
||||
add_menu_entry(m, "Move up", do_nothing,
|
||||
bind_gpbinding(CONFIG_GAMEPAD_KEY_UP)
|
||||
|
@ -512,18 +587,29 @@ static void options_sub_gamepad_controls(MenuData *parent, void *arg) {
|
|||
);
|
||||
|
||||
add_menu_separator(m);
|
||||
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Back", menu_action_close, NULL);
|
||||
|
||||
menu_loop(m);
|
||||
parent->frames = 0;
|
||||
return m;
|
||||
}
|
||||
|
||||
static void options_sub_gamepad(MenuData *parent, void *arg) {
|
||||
MenuData menu, *m;
|
||||
OptionBinding *b;
|
||||
m = &menu;
|
||||
static void destroy_options_menu_gamepad(MenuData *m) {
|
||||
OptionsMenuContext *ctx = m->context;
|
||||
|
||||
create_options_menu_basic(m, "Gamepad Options");
|
||||
if(config_get_int(CONFIG_GAMEPAD_ENABLED) && strcasecmp(config_get_str(CONFIG_GAMEPAD_DEVICE), ctx->data)) {
|
||||
gamepad_restart();
|
||||
}
|
||||
|
||||
destroy_options_menu(m);
|
||||
}
|
||||
|
||||
static MenuData* create_options_menu_gamepad(MenuData *parent) {
|
||||
MenuData *m = create_options_menu_base("Gamepad Options");
|
||||
m->end = destroy_options_menu_gamepad;
|
||||
|
||||
OptionsMenuContext *ctx = m->context;
|
||||
ctx->data = strdup(config_get_str(CONFIG_GAMEPAD_DEVICE));
|
||||
|
||||
OptionBinding *b;
|
||||
|
||||
add_menu_entry(m, "Enable Gamepad/Joystick support", do_nothing,
|
||||
b = bind_option(CONFIG_GAMEPAD_ENABLED, bind_common_onoff_get, bind_common_onoff_set)
|
||||
|
@ -531,10 +617,10 @@ static void options_sub_gamepad(MenuData *parent, void *arg) {
|
|||
|
||||
add_menu_entry(m, "Device", do_nothing,
|
||||
b = bind_gpdevice(CONFIG_GAMEPAD_DEVICE)
|
||||
); bind_setdependence(b, gamepad_enabled_depencence);
|
||||
); b->dependence = gamepad_enabled_depencence;
|
||||
|
||||
add_menu_separator(m);
|
||||
add_menu_entry(m, "Customize controls…", options_sub_gamepad_controls, NULL);
|
||||
add_menu_entry(m, "Customize controls…", enter_options_menu_gamepad_controls, NULL);
|
||||
|
||||
add_menu_separator(m);
|
||||
|
||||
|
@ -564,25 +650,13 @@ static void options_sub_gamepad(MenuData *parent, void *arg) {
|
|||
);
|
||||
|
||||
add_menu_separator(m);
|
||||
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Back", menu_action_close, NULL);
|
||||
|
||||
char *gpdev = strdup(config_get_str(CONFIG_GAMEPAD_DEVICE));
|
||||
|
||||
menu_loop(m);
|
||||
parent->frames = 0;
|
||||
|
||||
if(config_get_int(CONFIG_GAMEPAD_ENABLED) && strcasecmp(config_get_str(CONFIG_GAMEPAD_DEVICE), gpdev)) {
|
||||
gamepad_restart();
|
||||
}
|
||||
|
||||
free(gpdev);
|
||||
return m;
|
||||
}
|
||||
|
||||
static void options_sub_controls(MenuData *parent, void *arg) {
|
||||
MenuData menu, *m;
|
||||
m = &menu;
|
||||
|
||||
create_options_menu_basic(m, "Controls");
|
||||
static MenuData* create_options_menu_controls(MenuData *parent) {
|
||||
MenuData *m = create_options_menu_base("Controls");
|
||||
|
||||
add_menu_entry(m, "Move up", do_nothing,
|
||||
bind_keybinding(CONFIG_KEY_UP)
|
||||
|
@ -677,17 +751,15 @@ static void options_sub_controls(MenuData *parent, void *arg) {
|
|||
#endif
|
||||
|
||||
add_menu_separator(m);
|
||||
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Back", menu_action_close, NULL);
|
||||
|
||||
menu_loop(m);
|
||||
parent->frames = 0;
|
||||
return m;
|
||||
}
|
||||
|
||||
void create_options_menu(MenuData *m) {
|
||||
MenuData* create_options_menu(void) {
|
||||
MenuData *m = create_options_menu_base("Options");
|
||||
OptionBinding *b;
|
||||
|
||||
create_options_menu_basic(m, "Options");
|
||||
|
||||
add_menu_entry(m, "Player name", do_nothing,
|
||||
b = bind_stroption(CONFIG_PLAYERNAME)
|
||||
);
|
||||
|
@ -725,23 +797,25 @@ void create_options_menu(MenuData *m) {
|
|||
|
||||
add_menu_entry(m, "SFX Volume", do_nothing,
|
||||
b = bind_scale(CONFIG_SFX_VOLUME, 0, 1, 0.1)
|
||||
); bind_setdependence(b, audio_output_works);
|
||||
); b->dependence = audio_output_works;
|
||||
|
||||
add_menu_entry(m, "BGM Volume", do_nothing,
|
||||
b = bind_scale(CONFIG_BGM_VOLUME, 0, 1, 0.1)
|
||||
); bind_setdependence(b, audio_output_works);
|
||||
); b->dependence = audio_output_works;
|
||||
|
||||
add_menu_entry(m, "Mute audio", do_nothing,
|
||||
b = bind_option(CONFIG_MUTE_AUDIO, bind_common_onoff_get, bind_common_onoff_set)
|
||||
); bind_onoff(b);
|
||||
|
||||
add_menu_separator(m);
|
||||
add_menu_entry(m, "Video options…", options_sub_video, NULL);
|
||||
add_menu_entry(m, "Customize controls…", options_sub_controls, NULL);
|
||||
add_menu_entry(m, "Gamepad & Joystick options…", options_sub_gamepad, NULL);
|
||||
add_menu_entry(m, "Video options…", enter_options_menu_video, NULL);
|
||||
add_menu_entry(m, "Customize controls…", enter_options_menu_controls, NULL);
|
||||
add_menu_entry(m, "Gamepad & Joystick options…", enter_options_menu_gamepad, NULL);
|
||||
add_menu_separator(m);
|
||||
|
||||
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Back", menu_action_close, NULL);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
// --- Drawing the menu --- //
|
||||
|
@ -766,9 +840,11 @@ static void update_options_menu(MenuData *menu) {
|
|||
}
|
||||
}
|
||||
|
||||
void draw_options_menu(MenuData *menu) {
|
||||
static void draw_options_menu(MenuData *menu) {
|
||||
OptionsMenuContext *ctx = menu->context;
|
||||
|
||||
draw_options_menu_bg(menu);
|
||||
draw_menu_title(menu, menu->context);
|
||||
draw_menu_title(menu, ctx->title);
|
||||
|
||||
r_mat_push();
|
||||
r_mat_translate(100, 100, 0);
|
||||
|
@ -1350,7 +1426,7 @@ static bool options_input_handler(SDL_Event *event, void *arg) {
|
|||
}
|
||||
#undef SHOULD_SKIP
|
||||
|
||||
void options_menu_input(MenuData *menu) {
|
||||
static void options_menu_input(MenuData *menu) {
|
||||
OptionBinding *b;
|
||||
EventFlags flags = EFLAG_MENU;
|
||||
|
||||
|
|
|
@ -13,48 +13,7 @@
|
|||
|
||||
#include "menu.h"
|
||||
|
||||
void create_options_menu(MenuData *m);
|
||||
void draw_options_menu(MenuData *m);
|
||||
|
||||
typedef struct OptionBinding OptionBinding;
|
||||
|
||||
typedef int (*BindingGetter)(OptionBinding*);
|
||||
typedef int (*BindingSetter)(OptionBinding*, int);
|
||||
typedef bool (*BindingDependence)(void);
|
||||
|
||||
typedef enum BindingType {
|
||||
BT_IntValue,
|
||||
BT_KeyBinding,
|
||||
BT_StrValue,
|
||||
BT_Resolution,
|
||||
BT_Scale,
|
||||
BT_GamepadKeyBinding,
|
||||
BT_GamepadAxisBinding,
|
||||
BT_GamepadDevice,
|
||||
} BindingType;
|
||||
|
||||
typedef struct OptionBinding {
|
||||
union {
|
||||
char **values;
|
||||
char *strvalue;
|
||||
};
|
||||
bool displaysingle;
|
||||
int valcount;
|
||||
int valrange_min;
|
||||
int valrange_max;
|
||||
float scale_min;
|
||||
float scale_max;
|
||||
float scale_step;
|
||||
BindingGetter getter;
|
||||
BindingSetter setter;
|
||||
BindingDependence dependence;
|
||||
int selected;
|
||||
int configentry;
|
||||
BindingType type;
|
||||
bool blockinput;
|
||||
int pad;
|
||||
} OptionBinding;
|
||||
|
||||
MenuData* create_options_menu(void);
|
||||
void draw_options_menu_bg(MenuData*);
|
||||
|
||||
#endif // IGUARD_menu_options_h
|
||||
|
|
|
@ -54,14 +54,16 @@ typedef struct {
|
|||
int stgnum;
|
||||
} startrpy_arg_t;
|
||||
|
||||
static void really_start_replay(void *varg) {
|
||||
startrpy_arg_t arg;
|
||||
memcpy(&arg, varg, sizeof(arg));
|
||||
free(varg);
|
||||
replay_play(arg.rpy, arg.stgnum);
|
||||
static void on_replay_finished(CallChainResult ccr) {
|
||||
start_bgm("menu");
|
||||
}
|
||||
|
||||
static void really_start_replay(void *varg) {
|
||||
startrpy_arg_t arg = *(startrpy_arg_t*)varg;
|
||||
free(varg);
|
||||
replay_play(arg.rpy, arg.stgnum, CALLCHAIN(on_replay_finished, NULL));
|
||||
}
|
||||
|
||||
static void start_replay(MenuData *menu, void *arg) {
|
||||
ReplayviewItemContext *ictx = arg;
|
||||
ReplayviewContext *mctx = menu->context;
|
||||
|
@ -109,10 +111,9 @@ static void replayview_draw_stagemenu(MenuData*);
|
|||
static void replayview_draw_messagebox(MenuData*);
|
||||
|
||||
static MenuData* replayview_sub_stageselect(MenuData *parent, ReplayviewItemContext *ictx) {
|
||||
MenuData *m = malloc(sizeof(MenuData));
|
||||
MenuData *m = alloc_menu();
|
||||
Replay *rpy = ictx->replay;
|
||||
|
||||
create_menu(m);
|
||||
m->draw = replayview_draw_stagemenu;
|
||||
m->flags = MF_Transient | MF_Abortable;
|
||||
m->transition = NULL;
|
||||
|
@ -126,13 +127,12 @@ static MenuData* replayview_sub_stageselect(MenuData *parent, ReplayviewItemCont
|
|||
}
|
||||
|
||||
static MenuData* replayview_sub_messagebox(MenuData *parent, const char *message) {
|
||||
MenuData *m = malloc(sizeof(MenuData));
|
||||
create_menu(m);
|
||||
MenuData *m = alloc_menu();
|
||||
m->draw = replayview_draw_messagebox;
|
||||
m->flags = MF_Transient | MF_Abortable;
|
||||
m->transition = NULL;
|
||||
m->context = parent->context;
|
||||
add_menu_entry(m, message, menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, message, menu_action_close, NULL);
|
||||
return m;
|
||||
}
|
||||
|
||||
|
@ -321,8 +321,7 @@ static void replayview_logic(MenuData *m) {
|
|||
|
||||
if(sm->state == MS_Dead) {
|
||||
if(ctx->sub_fade == 1.0) {
|
||||
destroy_menu(sm);
|
||||
free(sm);
|
||||
free_menu(sm);
|
||||
ctx->submenu = ctx->next_submenu;
|
||||
ctx->next_submenu = NULL;
|
||||
return;
|
||||
|
@ -427,16 +426,8 @@ static void replayview_free(MenuData *m) {
|
|||
if(m->context) {
|
||||
ReplayviewContext *ctx = m->context;
|
||||
|
||||
if(ctx->submenu) {
|
||||
destroy_menu(ctx->submenu);
|
||||
free(ctx->submenu);
|
||||
}
|
||||
|
||||
if(ctx->next_submenu) {
|
||||
destroy_menu(ctx->next_submenu);
|
||||
free(ctx->next_submenu);
|
||||
}
|
||||
|
||||
free_menu(ctx->next_submenu);
|
||||
free_menu(ctx->submenu);
|
||||
free(m->context);
|
||||
m->context = NULL;
|
||||
}
|
||||
|
@ -448,8 +439,9 @@ static void replayview_free(MenuData *m) {
|
|||
}
|
||||
}
|
||||
|
||||
void create_replayview_menu(MenuData *m) {
|
||||
create_menu(m);
|
||||
MenuData* create_replayview_menu(void) {
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->logic = replayview_logic;
|
||||
m->input = replayview_menu_input;
|
||||
m->draw = replayview_draw;
|
||||
|
@ -466,11 +458,13 @@ void create_replayview_menu(MenuData *m) {
|
|||
int r = fill_replayview_menu(m);
|
||||
|
||||
if(!r) {
|
||||
add_menu_entry(m, "No replays available. Play the game and record some!", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "No replays available. Play the game and record some!", menu_action_close, NULL);
|
||||
} else if(r < 0) {
|
||||
add_menu_entry(m, "There was a problem getting the replay list :(", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "There was a problem getting the replay list :(", menu_action_close, NULL);
|
||||
} else {
|
||||
add_menu_separator(m);
|
||||
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Back", menu_action_close, NULL);
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
|
||||
#include "menu.h"
|
||||
|
||||
void create_replayview_menu(MenuData *m);
|
||||
MenuData* create_replayview_menu(void);
|
||||
|
||||
#endif // IGUARD_menu_replayview_h
|
||||
|
|
|
@ -111,35 +111,33 @@ static void update_saverpy_menu(MenuData *m) {
|
|||
}
|
||||
}
|
||||
|
||||
void create_saverpy_menu(MenuData *m) {
|
||||
create_menu(m);
|
||||
static MenuData* create_saverpy_menu(void) {
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->input = saverpy_menu_input;
|
||||
m->draw = draw_saverpy_menu;
|
||||
m->logic = update_saverpy_menu;
|
||||
m->flags = MF_Transient;
|
||||
|
||||
add_menu_entry(m, "Yes", save_rpy, NULL);
|
||||
add_menu_entry(m, "No", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "No", menu_action_close, NULL);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
void ask_save_replay(void) {
|
||||
void ask_save_replay(CallChain next) {
|
||||
assert(global.replay_stage != NULL);
|
||||
|
||||
switch(config_get_int(CONFIG_SAVE_RPY)) {
|
||||
case 0: {
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
case 1:
|
||||
do_save_replay(&global.replay);
|
||||
// fallthrough
|
||||
case 0:
|
||||
run_call_chain(&next, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
MenuData m;
|
||||
create_saverpy_menu(&m);
|
||||
menu_loop(&m);
|
||||
case 2:
|
||||
enter_menu(create_saverpy_menu(), next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
#include "taisei.h"
|
||||
|
||||
#include "menu.h"
|
||||
#include "eventloop/eventloop.h"
|
||||
|
||||
void create_saverpy_menu(MenuData*);
|
||||
void ask_save_replay(void);
|
||||
void ask_save_replay(CallChain next);
|
||||
|
||||
#endif // IGUARD_menu_savereplay_h
|
||||
|
|
|
@ -19,11 +19,12 @@ static void draw_spell_menu(MenuData *m) {
|
|||
draw_menu_list(m, 100, 100, NULL);
|
||||
}
|
||||
|
||||
void create_spell_menu(MenuData *m) {
|
||||
MenuData* create_spell_menu(void) {
|
||||
char title[128];
|
||||
Difficulty lastdiff = D_Any;
|
||||
|
||||
create_menu(m);
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->draw = draw_spell_menu;
|
||||
m->logic = animate_menu_list;
|
||||
m->flags = MF_Abortable;
|
||||
|
@ -51,9 +52,11 @@ void create_spell_menu(MenuData *m) {
|
|||
}
|
||||
|
||||
add_menu_separator(m);
|
||||
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Back", menu_action_close, NULL);
|
||||
|
||||
while(!m->entries[m->cursor].action) {
|
||||
++m->cursor;
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
|
||||
#include "menu.h"
|
||||
|
||||
void create_spell_menu(MenuData *m);
|
||||
MenuData* create_spell_menu(void);
|
||||
|
||||
#endif // IGUARD_menu_spellpractice_h
|
||||
|
|
|
@ -19,10 +19,11 @@ static void draw_stgpract_menu(MenuData *m) {
|
|||
draw_menu_list(m, 100, 100, NULL);
|
||||
}
|
||||
|
||||
void create_stgpract_menu(MenuData *m, Difficulty diff) {
|
||||
MenuData* create_stgpract_menu(Difficulty diff) {
|
||||
char title[128];
|
||||
|
||||
create_menu(m);
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->draw = draw_stgpract_menu;
|
||||
m->logic = animate_menu_list;
|
||||
m->flags = MF_Abortable;
|
||||
|
@ -45,9 +46,11 @@ void create_stgpract_menu(MenuData *m, Difficulty diff) {
|
|||
}
|
||||
|
||||
add_menu_separator(m);
|
||||
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Back", menu_action_close, NULL);
|
||||
|
||||
while(!m->entries[m->cursor].action) {
|
||||
++m->cursor;
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,6 @@
|
|||
#include "menu.h"
|
||||
#include "difficulty.h"
|
||||
|
||||
void create_stgpract_menu(MenuData *m, Difficulty diff);
|
||||
MenuData* create_stgpract_menu(Difficulty diff);
|
||||
|
||||
#endif // IGUARD_menu_stagepractice_h
|
||||
|
|
|
@ -21,11 +21,12 @@ static void draw_stage_menu(MenuData *m) {
|
|||
draw_menu_list(m, 100, 100, NULL);
|
||||
}
|
||||
|
||||
void create_stage_menu(MenuData *m) {
|
||||
MenuData* create_stage_menu(void) {
|
||||
char title[STGMENU_MAX_TITLE_LENGTH];
|
||||
Difficulty lastdiff = D_Any;
|
||||
|
||||
create_menu(m);
|
||||
MenuData *m = alloc_menu();
|
||||
|
||||
m->draw = draw_stage_menu;
|
||||
m->logic = animate_menu_list;
|
||||
m->flags = MF_Abortable;
|
||||
|
@ -43,5 +44,7 @@ void create_stage_menu(MenuData *m) {
|
|||
}
|
||||
|
||||
add_menu_separator(m);
|
||||
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
|
||||
add_menu_entry(m, "Back", menu_action_close, NULL);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@
|
|||
|
||||
#define STGMENU_MAX_TITLE_LENGTH 128
|
||||
|
||||
void create_stage_menu(MenuData *m);
|
||||
MenuData* create_stage_menu(void);
|
||||
|
||||
#endif // IGUARD_menu_stageselect_h
|
||||
|
|
|
@ -18,42 +18,41 @@
|
|||
#include "global.h"
|
||||
#include "submenus.h"
|
||||
|
||||
void enter_options(MenuData *menu, void *arg) {
|
||||
MenuData m;
|
||||
create_options_menu(&m);
|
||||
menu_loop(&m);
|
||||
static void on_leave_options(CallChainResult ccr) {
|
||||
MenuData *m = ccr.result;
|
||||
|
||||
if(m->state == MS_Dead) {
|
||||
taisei_commit_persistent_data();
|
||||
}
|
||||
}
|
||||
|
||||
void enter_stagemenu(MenuData *menu, void *arg) {
|
||||
MenuData m;
|
||||
create_stage_menu(&m);
|
||||
menu_loop(&m);
|
||||
void menu_action_enter_options(MenuData *menu, void *arg) {
|
||||
enter_menu(create_options_menu(), CALLCHAIN(on_leave_options, NULL));
|
||||
}
|
||||
|
||||
void enter_replayview(MenuData *menu, void *arg) {
|
||||
MenuData m;
|
||||
create_replayview_menu(&m);
|
||||
menu_loop(&m);
|
||||
void menu_action_enter_stagemenu(MenuData *menu, void *arg) {
|
||||
enter_menu(create_stage_menu(), NO_CALLCHAIN);
|
||||
}
|
||||
|
||||
void enter_spellpractice(MenuData *menu, void *arg) {
|
||||
MenuData m;
|
||||
create_spell_menu(&m);
|
||||
menu_loop(&m);
|
||||
void menu_action_enter_replayview(MenuData *menu, void *arg) {
|
||||
enter_menu(create_replayview_menu(), NO_CALLCHAIN);
|
||||
}
|
||||
|
||||
void enter_stagepractice(MenuData *menu, void *arg) {
|
||||
MenuData m;
|
||||
|
||||
do {
|
||||
create_difficulty_menu(&m);
|
||||
|
||||
if(menu_loop(&m) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
global.diff = progress.game_settings.difficulty;
|
||||
create_stgpract_menu(&m, global.diff);
|
||||
menu_loop(&m);
|
||||
} while(m.selected < 0 || m.selected == m.ecount - 1);
|
||||
void menu_action_enter_spellpractice(MenuData *menu, void *arg) {
|
||||
enter_menu(create_spell_menu(), NO_CALLCHAIN);
|
||||
}
|
||||
|
||||
static void stgpract_do_choose_stage(CallChainResult ccr);
|
||||
|
||||
void menu_action_enter_stagepractice(MenuData *menu, void *arg) {
|
||||
enter_menu(create_difficulty_menu(), CALLCHAIN(stgpract_do_choose_stage, NULL));
|
||||
}
|
||||
|
||||
static void stgpract_do_choose_stage(CallChainResult ccr) {
|
||||
MenuData *prev_menu = ccr.result;
|
||||
assert(prev_menu != NULL);
|
||||
|
||||
if(prev_menu->selected >= 0) {
|
||||
enter_menu(create_stgpract_menu(progress.game_settings.difficulty), NO_CALLCHAIN);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
|
||||
#include "taisei.h"
|
||||
|
||||
void enter_options(MenuData *menu, void *arg);
|
||||
void enter_stagemenu(MenuData *menu, void *arg);
|
||||
void enter_replayview(MenuData *menu, void *arg);
|
||||
void enter_spellpractice(MenuData *menu, void *arg);
|
||||
void enter_stagepractice(MenuData *menu, void *arg);
|
||||
void menu_action_enter_options(MenuData *menu, void *arg);
|
||||
void menu_action_enter_stagemenu(MenuData *menu, void *arg);
|
||||
void menu_action_enter_replayview(MenuData *menu, void *arg);
|
||||
void menu_action_enter_spellpractice(MenuData *menu, void *arg);
|
||||
void menu_action_enter_stagepractice(MenuData *menu, void *arg);
|
||||
|
||||
#endif // IGUARD_menu_submenus_h
|
||||
|
|
100
src/meson.build
100
src/meson.build
|
@ -102,6 +102,7 @@ sse42_src = []
|
|||
|
||||
subdir('audio')
|
||||
subdir('dialog')
|
||||
subdir('eventloop')
|
||||
subdir('menu')
|
||||
subdir('plrmodes')
|
||||
subdir('renderer')
|
||||
|
@ -132,6 +133,7 @@ configure_file(configuration : config, output : 'build_config.h')
|
|||
taisei_src += [
|
||||
audio_src,
|
||||
dialog_src,
|
||||
eventloop_src,
|
||||
menu_src,
|
||||
plrmodes_src,
|
||||
renderer_src,
|
||||
|
@ -148,17 +150,89 @@ taisei_deps += [
|
|||
util_deps,
|
||||
]
|
||||
|
||||
if macos_app_bundle
|
||||
taisei_exe_name = 'Taisei'
|
||||
else
|
||||
taisei_exe_name = 'taisei'
|
||||
endif
|
||||
taisei_basename = (macos_app_bundle ? 'Taisei' : 'taisei')
|
||||
|
||||
taisei_exe = executable(taisei_exe_name, taisei_src, version_deps,
|
||||
dependencies : taisei_deps,
|
||||
c_args : taisei_c_args,
|
||||
c_pch : 'pch/taisei_pch.h',
|
||||
gui_app : not get_option('win_console'),
|
||||
install : true,
|
||||
install_dir : bindir,
|
||||
)
|
||||
if host_machine.system() == 'emscripten'
|
||||
em_debug = get_option('debug')
|
||||
em_link_outputs = []
|
||||
em_link_output_suffixes = ['html', 'wasm', 'js'] # first element is significant
|
||||
em_data_dir = config.get_unquoted('TAISEI_BUILDCONF_DATA_PATH')
|
||||
em_link_args = [
|
||||
em_bundle_link_args,
|
||||
'--pre-js', em_preamble,
|
||||
'--shell-file', em_shell,
|
||||
'-s', 'ALLOW_MEMORY_GROWTH=1',
|
||||
'-s', 'ENVIRONMENT=web',
|
||||
'-s', 'ERROR_ON_MISSING_LIBRARIES=0',
|
||||
'-s', 'EXIT_RUNTIME=0',
|
||||
'-s', 'EXPORTED_RUNTIME_METHODS=["ccall"]',
|
||||
'-s', 'EXPORT_NAME=Taisei',
|
||||
'-s', 'FILESYSTEM=1',
|
||||
'-s', 'FORCE_FILESYSTEM=1',
|
||||
'-s', 'GL_POOL_TEMP_BUFFERS=0',
|
||||
'-s', 'LZ4=1',
|
||||
'-s', 'MODULARIZE=0',
|
||||
'-s', 'TOTAL_MEMORY=268435456',
|
||||
'-s', 'USE_WEBGL2=1',
|
||||
'-s', 'WASM=1',
|
||||
|
||||
# Try enabling this if unpatched Freetype crashes
|
||||
# '-s', 'EMULATE_FUNCTION_POINTER_CASTS=1',
|
||||
]
|
||||
|
||||
if em_debug
|
||||
em_link_output_suffixes += ['wast']
|
||||
em_link_args += [
|
||||
'--emrun',
|
||||
'--profiling',
|
||||
'-O0',
|
||||
'-g3',
|
||||
'-s', 'ASSERTIONS=2',
|
||||
'-s', 'GL_DEBUG=1',
|
||||
]
|
||||
else
|
||||
em_link_args += [
|
||||
'--llvm-lto', (get_option('b_lto') ? '3' : '0'),
|
||||
'-O@0@'.format(get_option('optimization')),
|
||||
'-g0',
|
||||
'-s', 'ASSERTIONS=0',
|
||||
]
|
||||
endif
|
||||
|
||||
foreach suffix : em_link_output_suffixes
|
||||
em_link_outputs += ['@0@.@1@'.format(taisei_basename, suffix)]
|
||||
endforeach
|
||||
|
||||
taisei = executable('@0@.bc'.format(taisei_basename), taisei_src, version_deps,
|
||||
dependencies : taisei_deps,
|
||||
c_args : taisei_c_args,
|
||||
install : false,
|
||||
)
|
||||
|
||||
taisei_html = custom_target(em_link_outputs[0],
|
||||
# NOTE: Unfortunately we can't just put 'taisei' directly into the command array.
|
||||
# Meson then makes an invalid assumption that we are going to execute it ("use as a generator"),
|
||||
# and aborts because there's no exe wrapper in the cross file (which wouldn't make sense to have).
|
||||
|
||||
command : [
|
||||
cc.cmd_array(),
|
||||
taisei.full_path(),
|
||||
em_link_args,
|
||||
'-o', '@OUTPUT0@'
|
||||
],
|
||||
build_by_default : true,
|
||||
depends : [taisei],
|
||||
output : em_link_outputs,
|
||||
install : true,
|
||||
install_dir : bindir,
|
||||
)
|
||||
else
|
||||
taisei = executable(taisei_basename, taisei_src, version_deps,
|
||||
dependencies : taisei_deps,
|
||||
c_args : taisei_c_args,
|
||||
c_pch : 'pch/taisei_pch.h',
|
||||
gui_app : not get_option('win_console'),
|
||||
install : true,
|
||||
install_dir : bindir,
|
||||
)
|
||||
endif
|
||||
|
|
|
@ -580,7 +580,6 @@ void r_vertex_array_layout(VertexArray *varr, uint nattribs, VertexAttribFormat
|
|||
}
|
||||
|
||||
void r_vsync(VsyncMode mode) {
|
||||
_r_state_touch_vsync();
|
||||
B.vsync(mode);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ typedef struct GLSLParseState {
|
|||
ShaderSource *src;
|
||||
SDL_RWops *dest;
|
||||
bool version_defined;
|
||||
char *linebuf;
|
||||
size_t linebuf_size;
|
||||
} GLSLParseState;
|
||||
|
||||
typedef struct GLSLFileParseState {
|
||||
|
@ -158,14 +160,13 @@ static bool glsl_process_file(GLSLFileParseState *fstate) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO: Remove this dumb limitation; also handle comments and line continuations properly.
|
||||
char linebuf[1024];
|
||||
// TODO: Handle comments and line continuations properly.
|
||||
|
||||
++fstate->lineno;
|
||||
glsl_write_lineno(fstate);
|
||||
|
||||
while(SDL_RWgets(stream, linebuf, sizeof(linebuf))) {
|
||||
char *p = linebuf;
|
||||
while(SDL_RWgets_realloc(stream, &fstate->global->linebuf, &fstate->global->linebuf_size)) {
|
||||
char *p = fstate->global->linebuf;
|
||||
skip_space(&p);
|
||||
|
||||
if(check_directive(p, INCLUDE_DIRECTIVE)) {
|
||||
|
@ -232,7 +233,7 @@ static bool glsl_process_file(GLSLFileParseState *fstate) {
|
|||
);
|
||||
fstate->global->src->lang.glsl.version = opt_v;
|
||||
} else {
|
||||
log_warn(
|
||||
log_debug(
|
||||
"%s:%d: source overrides version to %s (default is %s)",
|
||||
fstate->path, fstate->lineno, buf_shader, buf_opt
|
||||
);
|
||||
|
@ -250,7 +251,7 @@ static bool glsl_process_file(GLSLFileParseState *fstate) {
|
|||
return false;
|
||||
}
|
||||
|
||||
SDL_RWwrite(dest, linebuf, 1, strlen(linebuf));
|
||||
SDL_RWwrite(dest, fstate->global->linebuf, 1, strlen(fstate->global->linebuf));
|
||||
}
|
||||
|
||||
fstate->lineno++;
|
||||
|
@ -276,12 +277,15 @@ bool glsl_load_source(const char *path, ShaderSource *out, const GLSLSourceOptio
|
|||
pstate.dest = out_buf;
|
||||
pstate.src = out;
|
||||
pstate.options = options;
|
||||
pstate.linebuf_size = 128;
|
||||
pstate.linebuf = calloc(1, pstate.linebuf_size);
|
||||
|
||||
GLSLFileParseState fstate = { 0 };
|
||||
fstate.global = &pstate;
|
||||
fstate.path = path;
|
||||
|
||||
bool result = glsl_process_file(&fstate);
|
||||
free(pstate.linebuf);
|
||||
|
||||
if(result) {
|
||||
SDL_WriteU8(out_buf, 0);
|
||||
|
|
|
@ -89,10 +89,6 @@ void r_state_pop(void) {
|
|||
B.framebuffer(S.framebuffer);
|
||||
}
|
||||
|
||||
RESTORE(RSTATE_VSYNC) {
|
||||
B.vsync(S.vsync);
|
||||
}
|
||||
|
||||
if(_r_state.head == _r_state.stack) {
|
||||
_r_state.head = NULL;
|
||||
} else {
|
||||
|
@ -150,9 +146,3 @@ void _r_state_touch_framebuffer(void) {
|
|||
S.framebuffer = B.framebuffer_current();
|
||||
});
|
||||
}
|
||||
|
||||
void _r_state_touch_vsync(void) {
|
||||
TAINT(RSTATE_VSYNC, {
|
||||
S.vsync = B.vsync_current();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
RSTATE(SHADER) \
|
||||
RSTATE(SHADER_UNIFORMS) \
|
||||
RSTATE(RENDERTARGET) \
|
||||
RSTATE(VSYNC) \
|
||||
|
||||
typedef enum RendererStateID {
|
||||
#define RSTATE(id) RSTATE_ID_##id,
|
||||
|
@ -52,7 +51,6 @@ typedef struct RendererStateRollback {
|
|||
ShaderProgram *shader;
|
||||
// TODO uniforms
|
||||
Framebuffer *framebuffer;
|
||||
VsyncMode vsync;
|
||||
} RendererStateRollback;
|
||||
|
||||
void _r_state_touch_capabilities(void);
|
||||
|
@ -64,7 +62,6 @@ void _r_state_touch_depth_func(void);
|
|||
void _r_state_touch_shader(void);
|
||||
void _r_state_touch_uniform(Uniform *uniform);
|
||||
void _r_state_touch_framebuffer(void);
|
||||
void _r_state_touch_vsync(void);
|
||||
|
||||
void _r_state_init(void);
|
||||
void _r_state_shutdown(void);
|
||||
|
|
|
@ -971,6 +971,14 @@ static void gl33_swap(SDL_Window *window) {
|
|||
gl33_sync_framebuffer();
|
||||
SDL_GL_SwapWindow(window);
|
||||
gl33_stats_post_frame();
|
||||
|
||||
if(glext.version.is_webgl) {
|
||||
// We can't rely on viewport being preserved across frames,
|
||||
// so force the next frame to set one on the first draw call.
|
||||
// The viewport might get updated externally when e.g. going
|
||||
// fullscreen, and we can't catch that in the resize event.
|
||||
memset(&R.viewport.active, 0, sizeof(R.viewport.active));
|
||||
}
|
||||
}
|
||||
|
||||
static void gl33_blend(BlendMode mode) {
|
||||
|
|
|
@ -333,7 +333,20 @@ static bool cache_uniforms(ShaderProgram *prog) {
|
|||
uni.cache.pending = calloc(uni.array_size, uni.elem_size);
|
||||
uni.cache.update_first_idx = uni.array_size;
|
||||
|
||||
type_to_accessors[uni.type].getter(&uni, size, uni.cache.commited);
|
||||
if(glext.version.is_webgl) {
|
||||
// Some browsers are pendatic about getting a null in GLctx.getUniform(),
|
||||
// so we'd have to be very careful and query each array index with
|
||||
// glGetUniformLocation in order to avoid an exception. Which is too much
|
||||
// hassle, so instead here's a hack that fills initial cache state with
|
||||
// some garbage that we'll not likely want to actually set.
|
||||
//
|
||||
// TODO: Might want to fix this properly if this issue ever actually
|
||||
// affects cases where we write to an array with an offset. But that's
|
||||
// probably not going to happen.
|
||||
memset(uni.cache.commited, 0xf0, uni.array_size * uni.elem_size);
|
||||
} else {
|
||||
type_to_accessors[uni.type].getter(&uni, size, uni.cache.commited);
|
||||
}
|
||||
|
||||
Uniform *new_uni = memdup(&uni, sizeof(uni));
|
||||
|
||||
|
|
|
@ -503,6 +503,7 @@ void glcommon_check_extensions(void) {
|
|||
|
||||
if(glext.version.is_es) {
|
||||
glext.version.is_ANGLE = strstr(glv, "(ANGLE ");
|
||||
glext.version.is_webgl = strstr(glv, "(WebGL ");
|
||||
}
|
||||
|
||||
log_info("OpenGL version: %s", glv);
|
||||
|
|
|
@ -71,6 +71,7 @@ struct glext_s {
|
|||
char minor;
|
||||
bool is_es;
|
||||
bool is_ANGLE;
|
||||
bool is_webgl;
|
||||
} version;
|
||||
|
||||
ext_flag_t base_instance;
|
||||
|
|
79
src/replay.c
79
src/replay.c
|
@ -642,6 +642,7 @@ bool replay_save(Replay *rpy, const char *name) {
|
|||
|
||||
bool result = replay_write(rpy, file, REPLAY_STRUCT_VERSION_WRITE);
|
||||
SDL_RWclose(file);
|
||||
vfs_sync(VFS_SYNC_STORE, NO_CALLCHAIN);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -783,45 +784,87 @@ int replay_find_stage_idx(Replay *rpy, uint8_t stageid) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
void replay_play(Replay *rpy, int firstidx) {
|
||||
typedef struct ReplayContext {
|
||||
CallChain cc;
|
||||
int stage_idx;
|
||||
} ReplayContext;
|
||||
|
||||
static void replay_do_cleanup(CallChainResult ccr);
|
||||
static void replay_do_play(CallChainResult ccr);
|
||||
static void replay_do_post_play(CallChainResult ccr);
|
||||
|
||||
void replay_play(Replay *rpy, int firstidx, CallChain next) {
|
||||
if(rpy != &global.replay) {
|
||||
replay_copy(&global.replay, rpy, true);
|
||||
}
|
||||
|
||||
if(firstidx >= global.replay.numstages || firstidx < 0) {
|
||||
log_error("No stage #%i in the replay", firstidx);
|
||||
replay_destroy(&global.replay);
|
||||
run_call_chain(&next, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
global.replaymode = REPLAY_PLAY;
|
||||
|
||||
for(int i = firstidx; i < global.replay.numstages; ++i) {
|
||||
ReplayStage *rstg = global.replay_stage = global.replay.stages+i;
|
||||
StageInfo *gstg = stage_get(rstg->stage);
|
||||
ReplayContext *ctx = calloc(1, sizeof(*ctx));
|
||||
ctx->cc = next;
|
||||
ctx->stage_idx = firstidx;
|
||||
|
||||
replay_do_play(CALLCHAIN_RESULT(ctx, NULL));
|
||||
}
|
||||
|
||||
static void replay_do_play(CallChainResult ccr) {
|
||||
ReplayContext *ctx = ccr.ctx;
|
||||
ReplayStage *rstg = NULL;
|
||||
StageInfo *gstg = NULL;
|
||||
|
||||
while(ctx->stage_idx < global.replay.numstages) {
|
||||
rstg = global.replay_stage = global.replay.stages + ctx->stage_idx++;
|
||||
gstg = stage_get(rstg->stage);
|
||||
|
||||
if(!gstg) {
|
||||
log_warn("Invalid stage %X in replay at %i skipped.", rstg->stage, i);
|
||||
log_warn("Invalid stage %X in replay at %i skipped.", rstg->stage, ctx->stage_idx);
|
||||
continue;
|
||||
}
|
||||
|
||||
global.plr.mode = plrmode_find(rstg->plr_char, rstg->plr_shot);
|
||||
stage_loop(gstg);
|
||||
|
||||
if(global.gameover == GAMEOVER_ABORT) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(global.gameover == GAMEOVER_RESTART) {
|
||||
rstg->desynced = false;
|
||||
--i;
|
||||
}
|
||||
|
||||
global.gameover = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if(gstg == NULL) {
|
||||
replay_do_cleanup(ccr);
|
||||
} else {
|
||||
global.plr.mode = plrmode_find(rstg->plr_char, rstg->plr_shot);
|
||||
stage_enter(gstg, CALLCHAIN(replay_do_post_play, ctx));
|
||||
}
|
||||
}
|
||||
|
||||
static void replay_do_post_play(CallChainResult ccr) {
|
||||
ReplayContext *ctx = ccr.ctx;
|
||||
|
||||
if(global.gameover == GAMEOVER_ABORT) {
|
||||
replay_do_cleanup(ccr);
|
||||
return;
|
||||
}
|
||||
|
||||
if(global.gameover == GAMEOVER_RESTART) {
|
||||
--ctx->stage_idx;
|
||||
}
|
||||
|
||||
global.gameover = 0;
|
||||
replay_do_play(ccr);
|
||||
}
|
||||
|
||||
static void replay_do_cleanup(CallChainResult ccr) {
|
||||
ReplayContext *ctx = ccr.ctx;
|
||||
|
||||
global.gameover = 0;
|
||||
global.replaymode = REPLAY_RECORD;
|
||||
replay_destroy(&global.replay);
|
||||
global.replay_stage = NULL;
|
||||
free_resources(false);
|
||||
|
||||
CallChain cc = ctx->cc;
|
||||
free(ctx);
|
||||
run_call_chain(&cc, NULL);
|
||||
}
|
||||
|
|
|
@ -242,7 +242,7 @@ bool replay_load_syspath(Replay *rpy, const char *path, ReplayReadMode mode);
|
|||
|
||||
void replay_copy(Replay *dst, Replay *src, bool steal_events);
|
||||
|
||||
void replay_play(Replay *rpy, int firstidx);
|
||||
void replay_play(Replay *rpy, int firstidx, CallChain next);
|
||||
|
||||
int replay_find_stage_idx(Replay *rpy, uint8_t stageid);
|
||||
|
||||
|
|
74
src/stage.c
74
src/stage.c
|
@ -25,6 +25,7 @@
|
|||
#include "stagetext.h"
|
||||
#include "stagedraw.h"
|
||||
#include "stageobjects.h"
|
||||
#include "eventloop/eventloop.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DPSTEST
|
||||
|
@ -240,13 +241,12 @@ static void stage_fade_bgm(void) {
|
|||
fade_bgm((FPS * FADE_TIME) / 2000.0);
|
||||
}
|
||||
|
||||
static void stage_ingame_menu_loop(MenuData *menu) {
|
||||
if(ingame_menu_interrupts_bgm()) {
|
||||
stop_bgm(false);
|
||||
}
|
||||
static void stage_leave_ingame_menu(CallChainResult ccr) {
|
||||
MenuData *m = ccr.result;
|
||||
|
||||
pause_sounds();
|
||||
menu_loop(menu);
|
||||
if(m->state != MS_Dead) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(global.gameover > 0) {
|
||||
stop_sounds();
|
||||
|
@ -258,6 +258,19 @@ static void stage_ingame_menu_loop(MenuData *menu) {
|
|||
resume_sounds();
|
||||
resume_bgm();
|
||||
}
|
||||
|
||||
CallChain *cc = ccr.ctx;
|
||||
run_call_chain(cc, NULL);
|
||||
free(cc);
|
||||
}
|
||||
|
||||
static void stage_enter_ingame_menu(MenuData *m, CallChain next) {
|
||||
if(ingame_menu_interrupts_bgm()) {
|
||||
stop_bgm(false);
|
||||
}
|
||||
|
||||
pause_sounds();
|
||||
enter_menu(m, CALLCHAIN(stage_leave_ingame_menu, memdup(&next, sizeof(next))));
|
||||
}
|
||||
|
||||
void stage_pause(void) {
|
||||
|
@ -265,15 +278,12 @@ void stage_pause(void) {
|
|||
return;
|
||||
}
|
||||
|
||||
MenuData menu;
|
||||
|
||||
if(global.replaymode == REPLAY_PLAY) {
|
||||
create_ingame_menu_replay(&menu);
|
||||
} else {
|
||||
create_ingame_menu(&menu);
|
||||
}
|
||||
|
||||
stage_ingame_menu_loop(&menu);
|
||||
stage_enter_ingame_menu(
|
||||
(global.replaymode == REPLAY_PLAY
|
||||
? create_ingame_menu_replay
|
||||
: create_ingame_menu
|
||||
)(), NO_CALLCHAIN
|
||||
);
|
||||
}
|
||||
|
||||
void stage_gameover(void) {
|
||||
|
@ -282,9 +292,7 @@ void stage_gameover(void) {
|
|||
return;
|
||||
}
|
||||
|
||||
MenuData menu;
|
||||
create_gameover_menu(&menu);
|
||||
stage_ingame_menu_loop(&menu);
|
||||
stage_enter_ingame_menu(create_gameover_menu(), NO_CALLCHAIN);
|
||||
}
|
||||
|
||||
static bool stage_input_common(SDL_Event *event, void *arg) {
|
||||
|
@ -635,6 +643,8 @@ typedef struct StageFrameState {
|
|||
StageInfo *stage;
|
||||
int transition_delay;
|
||||
uint16_t last_replay_fps;
|
||||
CallChain cc;
|
||||
int logic_calls;
|
||||
} StageFrameState;
|
||||
|
||||
static void stage_update_fps(StageFrameState *fstate) {
|
||||
|
@ -678,10 +688,12 @@ static void stage_give_clear_bonus(const StageInfo *stage, StageClearBonus *bonu
|
|||
player_add_points(&global.plr, bonus->total);
|
||||
}
|
||||
|
||||
static FrameAction stage_logic_frame(void *arg) {
|
||||
static LogicFrameAction stage_logic_frame(void *arg) {
|
||||
StageFrameState *fstate = arg;
|
||||
StageInfo *stage = fstate->stage;
|
||||
|
||||
++fstate->logic_calls;
|
||||
|
||||
stage_update_fps(fstate);
|
||||
|
||||
if(global.shake_view > 30) {
|
||||
|
@ -742,7 +754,7 @@ static FrameAction stage_logic_frame(void *arg) {
|
|||
return LFRAME_WAIT;
|
||||
}
|
||||
|
||||
static FrameAction stage_render_frame(void *arg) {
|
||||
static RenderFrameAction stage_render_frame(void *arg) {
|
||||
StageFrameState *fstate = arg;
|
||||
StageInfo *stage = fstate->stage;
|
||||
|
||||
|
@ -758,7 +770,9 @@ static FrameAction stage_render_frame(void *arg) {
|
|||
return RFRAME_SWAP;
|
||||
}
|
||||
|
||||
void stage_loop(StageInfo *stage) {
|
||||
static void stage_end_loop(void *ctx);
|
||||
|
||||
void stage_enter(StageInfo *stage, CallChain next) {
|
||||
assert(stage);
|
||||
assert(stage->procs);
|
||||
assert(stage->procs->preload);
|
||||
|
@ -772,6 +786,7 @@ void stage_loop(StageInfo *stage) {
|
|||
if(global.gameover == GAMEOVER_WIN) {
|
||||
global.gameover = 0;
|
||||
} else if(global.gameover) {
|
||||
run_call_chain(&next, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -834,8 +849,15 @@ void stage_loop(StageInfo *stage) {
|
|||
display_stage_title(stage);
|
||||
}
|
||||
|
||||
StageFrameState fstate = { .stage = stage };
|
||||
loop_at_fps(stage_logic_frame, stage_render_frame, &fstate, FPS);
|
||||
StageFrameState *fstate = calloc(1 , sizeof(*fstate));
|
||||
fstate->stage = stage;
|
||||
fstate->cc = next;
|
||||
|
||||
eventloop_enter(fstate, stage_logic_frame, stage_render_frame, stage_end_loop, FPS);
|
||||
}
|
||||
|
||||
void stage_end_loop(void* ctx) {
|
||||
StageFrameState *s = ctx;
|
||||
|
||||
if(global.replaymode == REPLAY_RECORD) {
|
||||
replay_stage_event(global.replay_stage, global.frames, EV_OVER, 0);
|
||||
|
@ -845,7 +867,7 @@ void stage_loop(StageInfo *stage) {
|
|||
}
|
||||
}
|
||||
|
||||
stage->procs->end();
|
||||
s->stage->procs->end();
|
||||
stage_draw_shutdown();
|
||||
stage_free();
|
||||
player_free(&global.plr);
|
||||
|
@ -854,8 +876,12 @@ void stage_loop(StageInfo *stage) {
|
|||
ent_shutdown();
|
||||
stage_objpools_free();
|
||||
stop_sounds();
|
||||
taisei_commit_persistent_data();
|
||||
|
||||
if(taisei_quit_requested()) {
|
||||
global.gameover = GAMEOVER_ABORT;
|
||||
}
|
||||
|
||||
run_call_chain(&s->cc, NULL);
|
||||
free(s);
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ StageProgress* stage_get_progress_from_info(StageInfo *stage, Difficulty diff, b
|
|||
void stage_init_array(void);
|
||||
void stage_free_array(void);
|
||||
|
||||
void stage_loop(StageInfo *stage);
|
||||
void stage_enter(StageInfo *stage, CallChain next);
|
||||
void stage_finish(int gameover);
|
||||
|
||||
void stage_pause(void);
|
||||
|
|
|
@ -170,6 +170,9 @@ typedef complex max_align_t;
|
|||
#else
|
||||
#define CMPLX(re,im) (_Complex double)((double)(re) + _Complex_I * (double)(im))
|
||||
#endif
|
||||
#elif defined __EMSCRIPTEN__ && defined __clang__
|
||||
// CMPLX from emscripten headers uses the clang-specific syntax without __extension__
|
||||
#pragma clang diagnostic ignored "-Wcomplex-component-init"
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
@ -197,10 +200,14 @@ typedef complex max_align_t;
|
|||
#define attr_sentinel \
|
||||
__attribute__ ((sentinel))
|
||||
|
||||
// Identifier is meant to be possibly unused.
|
||||
// Symbol is meant to be possibly unused.
|
||||
#define attr_unused \
|
||||
__attribute__ ((unused))
|
||||
|
||||
// Symbol should be emitted even if it appears to be unused.
|
||||
#define attr_used \
|
||||
__attribute__ ((used))
|
||||
|
||||
// Function or type is deprecated and should not be used.
|
||||
#define attr_deprecated(msg) \
|
||||
__attribute__ ((deprecated(msg)))
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
uint line;
|
||||
} DebugInfo;
|
||||
|
||||
#define _DEBUG_INFO_PTR_ (&(DebugInfo){ __FILE__, __func__, __LINE__ })
|
||||
#define _DEBUG_INFO_INITIALIZER_ { __FILE__, __func__, __LINE__ }
|
||||
#define _DEBUG_INFO_ ((DebugInfo) _DEBUG_INFO_INITIALIZER_)
|
||||
#define _DEBUG_INFO_PTR_ (&_DEBUG_INFO_)
|
||||
#define set_debug_info(debug) _set_debug_info(debug, _DEBUG_INFO_PTR_)
|
||||
void _set_debug_info(DebugInfo *debug, DebugInfo *meta);
|
||||
DebugInfo* get_debug_info(void);
|
||||
|
|
|
@ -62,6 +62,36 @@ char* SDL_RWgets(SDL_RWops *rwops, char *buf, size_t bufsize) {
|
|||
return buf;
|
||||
}
|
||||
|
||||
char* SDL_RWgets_realloc(SDL_RWops *rwops, char **buf, size_t *bufsize) {
|
||||
char c, *ptr = *buf, *end = *buf + *bufsize - 1;
|
||||
assert(end >= ptr);
|
||||
|
||||
while((c = SDL_ReadU8(rwops))) {
|
||||
*ptr++ = c;
|
||||
|
||||
if(ptr > end) {
|
||||
ptrdiff_t ofs = ptr - *buf;
|
||||
*bufsize *= 2;
|
||||
*buf = realloc(*buf, *bufsize);
|
||||
end = *buf + *bufsize - 1;
|
||||
ptr = *buf + ofs;
|
||||
*end = 0;
|
||||
}
|
||||
|
||||
if(c == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ptr == *buf)
|
||||
return NULL;
|
||||
|
||||
assert(ptr <= end);
|
||||
*ptr = 0;
|
||||
|
||||
return *buf;
|
||||
}
|
||||
|
||||
size_t SDL_RWprintf(SDL_RWops *rwops, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
char* read_all(const char *filename, int *size);
|
||||
|
||||
char* SDL_RWgets(SDL_RWops *rwops, char *buf, size_t bufsize);
|
||||
char* SDL_RWgets_realloc(SDL_RWops *rwops, char **buf, size_t *bufsize);
|
||||
size_t SDL_RWprintf(SDL_RWops *rwops, const char* fmt, ...) attr_printf(2, 3);
|
||||
|
||||
// This is for the very few legitimate uses for printf/fprintf that shouldn't be replaced with log_*
|
||||
|
|
|
@ -5,7 +5,6 @@ vfs_src = files(
|
|||
'private.c',
|
||||
'public.c',
|
||||
'readonly_wrapper.c',
|
||||
'setup.c',
|
||||
'syspath_public.c',
|
||||
'union.c',
|
||||
'union_public.c',
|
||||
|
@ -31,3 +30,11 @@ elif host_machine.system() == 'windows'
|
|||
else
|
||||
vfs_src += files('syspath_posix.c') # eeehh, maybe it'll work ¯\_(ツ)_/¯
|
||||
endif
|
||||
|
||||
if host_machine.system() == 'emscripten'
|
||||
vfs_src += files('setup_emscripten.c')
|
||||
vfs_src += files('sync_emscripten.c')
|
||||
else
|
||||
vfs_src += files('setup_generic.c')
|
||||
vfs_src += files('sync_noop.c')
|
||||
endif
|
||||
|
|
|
@ -76,6 +76,8 @@ static void* call_shutdown_hook(List **vlist, List *vhook, void *arg) {
|
|||
}
|
||||
|
||||
void vfs_shutdown(void) {
|
||||
vfs_sync(VFS_SYNC_STORE, NO_CALLCHAIN);
|
||||
|
||||
list_foreach(&shutdown_hooks, call_shutdown_hook, NULL);
|
||||
|
||||
vfs_decref(vfs_root);
|
||||
|
|
|
@ -197,7 +197,7 @@ const char* vfs_dir_read(VFSDir *dir) {
|
|||
return vfs_node_iter(dir->node, &dir->opaque);
|
||||
}
|
||||
|
||||
char** vfs_dir_list_sorted(const char *path, size_t *out_size, int (*compare)(const char**, const char**), bool (*filter)(const char*)) {
|
||||
char** vfs_dir_list_sorted(const char *path, size_t *out_size, int (*compare)(const void*, const void*), bool (*filter)(const char*)) {
|
||||
char **results = NULL;
|
||||
VFSDir *dir = vfs_dir_open(path);
|
||||
|
||||
|
@ -225,7 +225,7 @@ char** vfs_dir_list_sorted(const char *path, size_t *out_size, int (*compare)(co
|
|||
vfs_dir_close(dir);
|
||||
|
||||
if(*out_size) {
|
||||
qsort(results, *out_size, sizeof(char*), (int (*)(const void*, const void*)) compare);
|
||||
qsort(results, *out_size, sizeof(char*), compare);
|
||||
}
|
||||
|
||||
return results;
|
||||
|
@ -243,12 +243,12 @@ void vfs_dir_list_free(char **list, size_t size) {
|
|||
free(list);
|
||||
}
|
||||
|
||||
int vfs_dir_list_order_ascending(const char **a, const char **b) {
|
||||
return strcmp(*a, *b);
|
||||
int vfs_dir_list_order_ascending(const void *a, const void *b) {
|
||||
return strcmp(*(char**)a, *(char**)b);
|
||||
}
|
||||
|
||||
int vfs_dir_list_order_descending(const char **a, const char **b) {
|
||||
return strcmp(*b, *a);
|
||||
int vfs_dir_list_order_descending(const void *a, const void *b) {
|
||||
return strcmp(*(char**)b, *(char**)a);
|
||||
}
|
||||
|
||||
void* vfs_dir_walk(const char *path, void* (*visit)(const char *path, void *arg), void *arg) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "union_public.h"
|
||||
#include "zipfile_public.h"
|
||||
#include "readonly_wrapper_public.h"
|
||||
#include "eventloop/eventloop.h"
|
||||
|
||||
typedef struct VFSInfo {
|
||||
uchar error : 1;
|
||||
|
@ -33,6 +34,11 @@ typedef enum VFSOpenMode {
|
|||
VFS_MODE_SEEKABLE = 4,
|
||||
} VFSOpenMode;
|
||||
|
||||
typedef enum VFSSyncMode {
|
||||
VFS_SYNC_LOAD = 1,
|
||||
VFS_SYNC_STORE = 0,
|
||||
} VFSSyncMode;
|
||||
|
||||
#define VFS_MODE_RWMASK (VFS_MODE_READ | VFS_MODE_WRITE)
|
||||
|
||||
typedef struct VFSDir VFSDir;
|
||||
|
@ -52,11 +58,11 @@ const char* vfs_dir_read(VFSDir *dir) attr_nonnull(1);
|
|||
|
||||
void* vfs_dir_walk(const char *path, void* (*visit)(const char *path, void *arg), void *arg);
|
||||
|
||||
char** vfs_dir_list_sorted(const char *path, size_t *out_size, int (*compare)(const char**, const char**), bool (*filter)(const char*))
|
||||
char** vfs_dir_list_sorted(const char *path, size_t *out_size, int (*compare)(const void*, const void*), bool (*filter)(const char*))
|
||||
attr_nonnull(1, 2, 3) attr_nodiscard;
|
||||
void vfs_dir_list_free(char **list, size_t size);
|
||||
int vfs_dir_list_order_ascending(const char **a, const char **b);
|
||||
int vfs_dir_list_order_descending(const char **a, const char **b);
|
||||
int vfs_dir_list_order_ascending(const void *a, const void *b);
|
||||
int vfs_dir_list_order_descending(const void *a, const void *b);
|
||||
|
||||
char* vfs_repr(const char *path, bool try_syspath) attr_nonnull(1) attr_nodiscard;
|
||||
bool vfs_print_tree(SDL_RWops *dest, const char *path) attr_nonnull(1, 2);
|
||||
|
@ -66,4 +72,6 @@ void vfs_init(void);
|
|||
void vfs_shutdown(void);
|
||||
const char* vfs_get_error(void) attr_returns_nonnull;
|
||||
|
||||
void vfs_sync(VFSSyncMode mode, CallChain next);
|
||||
|
||||
#endif // IGUARD_vfs_public_h
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "util.h"
|
||||
#include "public.h"
|
||||
|
||||
void vfs_setup(bool silent);
|
||||
void vfs_setup(CallChain onready);
|
||||
|
||||
#endif // IGUARD_vfs_setup_h
|
||||
|
|
41
src/vfs/setup_emscripten.c
Normal file
41
src/vfs/setup_emscripten.c
Normal file
|
@ -0,0 +1,41 @@
|
|||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "public.h"
|
||||
#include "setup.h"
|
||||
#include "util.h"
|
||||
|
||||
static void vfs_setup_onsync(CallChainResult ccr) {
|
||||
const char *res_path = "/" TAISEI_BUILDCONF_DATA_PATH;
|
||||
const char *storage_path = "/persistent/storage";
|
||||
const char *cache_path = "/persistent/cache";
|
||||
|
||||
log_info("Resource path: %s", res_path);
|
||||
log_info("Storage path: %s", storage_path);
|
||||
log_info("Cache path: %s", cache_path);
|
||||
|
||||
if(!vfs_mount_syspath("/res", res_path, VFS_SYSPATH_MOUNT_READONLY)) {
|
||||
log_fatal("Failed to mount '%s': %s", res_path, vfs_get_error());
|
||||
}
|
||||
|
||||
if(!vfs_mount_syspath("/storage", storage_path, VFS_SYSPATH_MOUNT_MKDIR)) {
|
||||
log_fatal("Failed to mount '%s': %s", storage_path, vfs_get_error());
|
||||
}
|
||||
|
||||
if(!vfs_mount_syspath("/cache", cache_path, VFS_SYSPATH_MOUNT_MKDIR)) {
|
||||
log_fatal("Failed to mount '%s': %s", cache_path, vfs_get_error());
|
||||
}
|
||||
|
||||
vfs_mkdir_required("storage/replays");
|
||||
vfs_mkdir_required("storage/screenshots");
|
||||
|
||||
CallChain *next = ccr.ctx;
|
||||
run_call_chain(next, NULL);
|
||||
free(next);
|
||||
}
|
||||
|
||||
void vfs_setup(CallChain next) {
|
||||
vfs_init();
|
||||
CallChain *cc = memdup(&next, sizeof(next));
|
||||
vfs_sync(VFS_SYNC_LOAD, CALLCHAIN(vfs_setup_onsync, cc));
|
||||
}
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "build_config.h"
|
||||
#include "public.h"
|
||||
#include "setup.h"
|
||||
#include "error.h"
|
||||
|
@ -132,18 +131,18 @@ static void load_packages(const char *dir, const char *unionmp) {
|
|||
vfs_dir_list_free(paklist, numpaks);
|
||||
}
|
||||
|
||||
void vfs_setup(bool silent) {
|
||||
// NOTE: For simplicity, we will assume that vfs_sync is not needed in this backend.
|
||||
|
||||
void vfs_setup(CallChain next) {
|
||||
char *res_path, *storage_path, *cache_path;
|
||||
get_core_paths(&res_path, &storage_path, &cache_path);
|
||||
|
||||
char *local_res_path = strfmt("%s/resources", storage_path);
|
||||
|
||||
if(!silent) {
|
||||
log_info("Resource path: %s", res_path);
|
||||
log_info("Storage path: %s", storage_path);
|
||||
log_info("Local resource path: %s", local_res_path);
|
||||
log_info("Cache path: %s", cache_path);
|
||||
}
|
||||
log_info("Resource path: %s", res_path);
|
||||
log_info("Storage path: %s", storage_path);
|
||||
log_info("Local resource path: %s", local_res_path);
|
||||
log_info("Cache path: %s", cache_path);
|
||||
|
||||
struct mpoint_t {
|
||||
const char *dest; const char *syspath; bool loadpaks; uint flags;
|
||||
|
@ -215,4 +214,6 @@ void vfs_setup(bool silent) {
|
|||
|
||||
vfs_unmount("resdirs");
|
||||
vfs_unmount("respkgs");
|
||||
|
||||
run_call_chain(&next, NULL);
|
||||
}
|
32
src/vfs/sync_emscripten.c
Normal file
32
src/vfs/sync_emscripten.c
Normal file
|
@ -0,0 +1,32 @@
|
|||
|
||||
#include "public.h"
|
||||
#include "util.h"
|
||||
#include <emscripten.h>
|
||||
|
||||
void EMSCRIPTEN_KEEPALIVE vfs_sync_callback(bool is_load, char *error, CallChain *next);
|
||||
|
||||
void vfs_sync_callback(bool is_load, char *error, CallChain *next) {
|
||||
if(error) {
|
||||
if(is_load) {
|
||||
log_error("Couldn't load persistent storage from IndexedDB: %s", error);
|
||||
} else {
|
||||
log_error("Couldn't save persistent storage to IndexedDB: %s", error);
|
||||
}
|
||||
} else {
|
||||
if(is_load) {
|
||||
log_info("Loaded persistent storage from IndexedDB");
|
||||
} else {
|
||||
log_info("Saved persistent storage to IndexedDB");
|
||||
}
|
||||
}
|
||||
|
||||
run_call_chain(next, error);
|
||||
free(next);
|
||||
}
|
||||
|
||||
void vfs_sync(VFSSyncMode mode, CallChain next) {
|
||||
CallChain *cc = memdup(&next, sizeof(next));
|
||||
__extension__ (EM_ASM({
|
||||
SyncFS($0, $1);
|
||||
}, (mode == VFS_SYNC_LOAD), cc));
|
||||
}
|
6
src/vfs/sync_noop.c
Normal file
6
src/vfs/sync_noop.c
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
#include "public.h"
|
||||
|
||||
void vfs_sync(VFSSyncMode mode, CallChain next) {
|
||||
run_call_chain(&next, NULL);
|
||||
}
|
118
src/video.c
118
src/video.c
|
@ -23,6 +23,62 @@ typedef struct ScreenshotTaskData {
|
|||
Pixmap image;
|
||||
} ScreenshotTaskData;
|
||||
|
||||
VideoCapabilityState (*video_query_capability)(VideoCapability cap);
|
||||
|
||||
static VideoCapabilityState video_query_capability_generic(VideoCapability cap) {
|
||||
switch(cap) {
|
||||
case VIDEO_CAP_FULLSCREEN:
|
||||
return VIDEO_AVAILABLE;
|
||||
|
||||
case VIDEO_CAP_EXTERNAL_RESIZE:
|
||||
return video_is_fullscreen() ? VIDEO_CURRENTLY_UNAVAILABLE : VIDEO_AVAILABLE;
|
||||
|
||||
case VIDEO_CAP_CHANGE_RESOLUTION:
|
||||
if(video_is_fullscreen() && config_get_int(CONFIG_FULLSCREEN_DESKTOP)) {
|
||||
return VIDEO_CURRENTLY_UNAVAILABLE;
|
||||
} else {
|
||||
return VIDEO_AVAILABLE;
|
||||
}
|
||||
|
||||
case VIDEO_CAP_VSYNC_ADAPTIVE:
|
||||
return VIDEO_AVAILABLE;
|
||||
}
|
||||
|
||||
UNREACHABLE;
|
||||
}
|
||||
|
||||
static VideoCapabilityState video_query_capability_alwaysfullscreen(VideoCapability cap) {
|
||||
switch(cap) {
|
||||
case VIDEO_CAP_FULLSCREEN:
|
||||
return VIDEO_ALWAYS_ENABLED;
|
||||
|
||||
case VIDEO_CAP_EXTERNAL_RESIZE:
|
||||
return VIDEO_NEVER_AVAILABLE;
|
||||
|
||||
// XXX: Might not be actually working, but let's be optimistic.
|
||||
case VIDEO_CAP_CHANGE_RESOLUTION:
|
||||
return VIDEO_AVAILABLE;
|
||||
|
||||
case VIDEO_CAP_VSYNC_ADAPTIVE:
|
||||
return VIDEO_AVAILABLE;
|
||||
}
|
||||
|
||||
UNREACHABLE;
|
||||
}
|
||||
|
||||
static VideoCapabilityState video_query_capability_webcanvas(VideoCapability cap) {
|
||||
switch(cap) {
|
||||
case VIDEO_CAP_EXTERNAL_RESIZE:
|
||||
return VIDEO_NEVER_AVAILABLE;
|
||||
|
||||
case VIDEO_CAP_VSYNC_ADAPTIVE:
|
||||
return VIDEO_NEVER_AVAILABLE;
|
||||
|
||||
default:
|
||||
return video_query_capability_generic(cap);
|
||||
}
|
||||
}
|
||||
|
||||
static void video_add_mode(int width, int height) {
|
||||
if(video.modes) {
|
||||
for(uint i = 0; i < video.mcount; ++i) {
|
||||
|
@ -158,7 +214,7 @@ static void video_new_window(int w, int h, bool fs, bool resizable) {
|
|||
|
||||
if(fs) {
|
||||
flags |= get_fullscreen_flag();
|
||||
} else if(resizable) {
|
||||
} else if(resizable && video.backend != VIDEO_BACKEND_EMSCRIPTEN) {
|
||||
flags |= SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
|
||||
|
@ -195,7 +251,7 @@ static bool video_set_display_mode(int w, int h) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static void video_set_fullscreen(bool fullscreen) {
|
||||
static void video_set_fullscreen_internal(bool fullscreen) {
|
||||
uint32_t flags = fullscreen ? get_fullscreen_flag() : 0;
|
||||
events_pause_keyrepeat();
|
||||
|
||||
|
@ -218,9 +274,9 @@ void video_set_mode(int w, int h, bool fs, bool resizable) {
|
|||
if(w != video.current.width || h != video.current.height) {
|
||||
if(fs && !config_get_int(CONFIG_FULLSCREEN_DESKTOP)) {
|
||||
video_set_display_mode(w, h);
|
||||
video_set_fullscreen(fs);
|
||||
video_set_fullscreen_internal(fs);
|
||||
video_update_mode_settings();
|
||||
} else {
|
||||
} else if(video.backend == VIDEO_BACKEND_X11) {
|
||||
// XXX: I would like to use SDL_SetWindowSize for size changes, but apparently it's impossible to reliably detect
|
||||
// when it fails to actually resize the window. For example, a tiling WM (awesome) may be getting in its way
|
||||
// and we'd never know. SDL_GL_GetDrawableSize/SDL_GetWindowSize aren't helping as of SDL 2.0.5.
|
||||
|
@ -228,13 +284,24 @@ void video_set_mode(int w, int h, bool fs, bool resizable) {
|
|||
// There's not much to be done about it. We're at mercy of SDL here and SDL is at mercy of the WM.
|
||||
video_new_window(w, h, fs, resizable);
|
||||
return;
|
||||
} else if(video.backend == VIDEO_BACKEND_EMSCRIPTEN && !fs) {
|
||||
// Needed to work around various SDL bugs and HTML/DOM quirks...
|
||||
video_new_window(w, h, fs, resizable);
|
||||
return;
|
||||
} else {
|
||||
SDL_SetWindowSize(video.window, w, h);
|
||||
video_update_mode_settings();
|
||||
}
|
||||
}
|
||||
|
||||
video_set_fullscreen(fs);
|
||||
video_set_fullscreen_internal(fs);
|
||||
SDL_SetWindowResizable(video.window, resizable);
|
||||
}
|
||||
|
||||
void video_set_fullscreen(bool fullscreen) {
|
||||
video_set_mode(video.intended.width, video.intended.height, fullscreen, video_is_resizable());
|
||||
}
|
||||
|
||||
static void* video_screenshot_task(void *arg) {
|
||||
ScreenshotTaskData *tdata = arg;
|
||||
|
||||
|
@ -350,10 +417,6 @@ bool video_is_fullscreen(void) {
|
|||
return WINFLAGS_IS_FULLSCREEN(SDL_GetWindowFlags(video.window));
|
||||
}
|
||||
|
||||
bool video_can_change_resolution(void) {
|
||||
return !video_is_fullscreen() || !config_get_int(CONFIG_FULLSCREEN_DESKTOP);
|
||||
}
|
||||
|
||||
static void video_init_sdl(void) {
|
||||
// XXX: workaround for an SDL bug: https://bugzilla.libsdl.org/show_bug.cgi?id=4127
|
||||
SDL_SetHintWithPriority(SDL_HINT_FRAMEBUFFER_ACCELERATION, "0", SDL_HINT_OVERRIDE);
|
||||
|
@ -421,6 +484,8 @@ static void video_handle_resize(int w, int h) {
|
|||
|
||||
if(w < minw || h < minh) {
|
||||
log_warn("Bad resize: %ix%i is too small!", w, h);
|
||||
// FIXME: the video_new_window is actually a workaround for Wayland.
|
||||
// I'm not sure if it's necessary for anything else.
|
||||
video_new_window(video.intended.width, video.intended.height, false, video_is_resizable());
|
||||
return;
|
||||
}
|
||||
|
@ -453,12 +518,7 @@ static bool video_handle_config_event(SDL_Event *evt, void *arg) {
|
|||
|
||||
switch(evt->user.code) {
|
||||
case CONFIG_FULLSCREEN:
|
||||
video_set_mode(
|
||||
config_get_int(CONFIG_VID_WIDTH),
|
||||
config_get_int(CONFIG_VID_HEIGHT),
|
||||
val->i,
|
||||
config_get_int(CONFIG_VID_RESIZABLE)
|
||||
);
|
||||
video_set_fullscreen(val->i);
|
||||
break;
|
||||
|
||||
case CONFIG_VID_RESIZABLE:
|
||||
|
@ -477,7 +537,30 @@ void video_init(void) {
|
|||
bool fullscreen_available = false;
|
||||
|
||||
video_init_sdl();
|
||||
log_info("Using driver '%s'", SDL_GetCurrentVideoDriver());
|
||||
|
||||
const char *driver = SDL_GetCurrentVideoDriver();
|
||||
log_info("Using driver '%s'", driver);
|
||||
|
||||
video_query_capability = video_query_capability_generic;
|
||||
|
||||
if(!strcmp(driver, "x11")) {
|
||||
video.backend = VIDEO_BACKEND_X11;
|
||||
} else if(!strcmp(driver, "emscripten")) {
|
||||
video.backend = VIDEO_BACKEND_EMSCRIPTEN;
|
||||
video_query_capability = video_query_capability_webcanvas;
|
||||
|
||||
// We can not start in fullscreen in the browser properly, so disable it here.
|
||||
// Fullscreen is still accessible via the settings menu and the shortcut key.
|
||||
config_set_int(CONFIG_FULLSCREEN, false);
|
||||
} else if(!strcmp(driver, "KMSDRM")) {
|
||||
video.backend = VIDEO_BACKEND_KMSDRM;
|
||||
video_query_capability = video_query_capability_alwaysfullscreen;
|
||||
} else if(!strcmp(driver, "RPI")) {
|
||||
video.backend = VIDEO_BACKEND_RPI;
|
||||
video_query_capability = video_query_capability_alwaysfullscreen;
|
||||
} else {
|
||||
video.backend = VIDEO_BACKEND_OTHER;
|
||||
}
|
||||
|
||||
r_init();
|
||||
|
||||
|
@ -559,4 +642,7 @@ void video_shutdown(void) {
|
|||
void video_swap_buffers(void) {
|
||||
r_framebuffer(NULL);
|
||||
r_swap(video.window);
|
||||
|
||||
// XXX: Unfortunately, there seems to be no reliable way to sync this up with events
|
||||
config_set_int(CONFIG_FULLSCREEN, video_is_fullscreen());
|
||||
}
|
||||
|
|
28
src/video.h
28
src/video.h
|
@ -33,24 +33,48 @@ typedef struct VideoMode {
|
|||
int height;
|
||||
} VideoMode;
|
||||
|
||||
typedef enum VideoBackend {
|
||||
VIDEO_BACKEND_OTHER,
|
||||
VIDEO_BACKEND_X11,
|
||||
VIDEO_BACKEND_EMSCRIPTEN,
|
||||
VIDEO_BACKEND_KMSDRM,
|
||||
VIDEO_BACKEND_RPI,
|
||||
} VideoBackend;
|
||||
|
||||
typedef struct {
|
||||
VideoMode *modes;
|
||||
SDL_Window *window;
|
||||
int mcount;
|
||||
VideoMode intended;
|
||||
VideoMode current;
|
||||
SDL_Window *window;
|
||||
VideoBackend backend;
|
||||
} Video;
|
||||
|
||||
typedef enum VideoCapability {
|
||||
VIDEO_CAP_FULLSCREEN,
|
||||
VIDEO_CAP_EXTERNAL_RESIZE,
|
||||
VIDEO_CAP_CHANGE_RESOLUTION,
|
||||
VIDEO_CAP_VSYNC_ADAPTIVE,
|
||||
} VideoCapability;
|
||||
|
||||
typedef enum VideoCapabilityState {
|
||||
VIDEO_NEVER_AVAILABLE,
|
||||
VIDEO_AVAILABLE,
|
||||
VIDEO_ALWAYS_ENABLED,
|
||||
VIDEO_CURRENTLY_UNAVAILABLE,
|
||||
} VideoCapabilityState;
|
||||
|
||||
extern Video video;
|
||||
|
||||
void video_init(void);
|
||||
void video_shutdown(void);
|
||||
void video_set_mode(int w, int h, bool fs, bool resizable);
|
||||
void video_set_fullscreen(bool fullscreen);
|
||||
void video_get_viewport(IntRect *vp);
|
||||
void video_get_viewport_size(int *width, int *height);
|
||||
bool video_is_fullscreen(void);
|
||||
bool video_is_resizable(void);
|
||||
bool video_can_change_resolution(void);
|
||||
extern VideoCapabilityState (*video_query_capability)(VideoCapability cap);
|
||||
void video_take_screenshot(void);
|
||||
void video_swap_buffers(void);
|
||||
|
||||
|
|
Loading…
Reference in a new issue