diff --git a/.circleci/config.yml b/.circleci/config.yml index 491bdfd400..4c73d1e8ef 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,6 +40,9 @@ jobs: steps: - build: build-config: -Dwith_docs=true -Db_coverage=true + - run: + name: build animgen + command: ninja -vC build tools/animgen - persist_to_workspace: root: . paths: @@ -54,6 +57,10 @@ jobs: command: | ulimit -c unlimited + printf "\n::: test animgen :::\n" + build/tools/animgen data/animation_presets.conf >/dev/null 2>error.log + [ -s error.log ] && cat error.log && exit 1 + printf "\n::: Unit tests :::\n" ninja -vC build test diff --git a/.gitignore b/.gitignore index e870f6ef6f..e9136ce222 100644 --- a/.gitignore +++ b/.gitignore @@ -51,8 +51,10 @@ target !/tests/testcases/*.py # Misc files +.vscode *.conf !/tests/configs/*.conf +!/data/*.conf perf.data perf.data.old core.* diff --git a/data/animation_presets.conf b/data/animation_presets.conf new file mode 100644 index 0000000000..f1ff06c80a --- /dev/null +++ b/data/animation_presets.conf @@ -0,0 +1,165 @@ +disappear = { + opacity = { + duration = "placeholder0"; + start = "window-raw-opacity-before"; + end = "window-raw-opacity"; + }; + blur-opacity = "opacity"; + shadow-opacity = "opacity"; + offset-x = "(1 - scale-x) / 2 * window-width"; + offset-y = "(1 - scale-y) / 2 * window-height"; + scale-x = { + curve = "cubic-bezier(0.21, 0.02, 0.76, 0.36)"; + duration = "placeholder0"; + start = 1; + end = "placeholder1"; + }; + scale-y = "scale-x"; + shadow-scale-x = "scale-x"; + shadow-scale-y = "scale-y"; + shadow-offset-x = "offset-x"; + shadow-offset-y = "offset-y"; + + # See comments in tools/animgen.c for syntax + *knobs = { + scale = 0.95; + duration = 0.2; + }; + *placeholders = ((0, "duration"),(1, "scale")); +}; + +appear = { + opacity = { + duration = "placeholder0"; + start = "window-raw-opacity-before"; + end = "window-raw-opacity"; + }; + blur-opacity = "opacity"; + shadow-opacity = "opacity"; + offset-x = "(1 - scale-x) / 2 * window-width"; + offset-y = "(1 - scale-y) / 2 * window-height"; + scale-x = { + curve = "cubic-bezier(0.24, 0.64, 0.79, 0.98)"; + duration = "placeholder0"; + start = "placeholder1"; + end = 1; + }; + scale-y = "scale-x"; + shadow-scale-x = "scale-x"; + shadow-scale-y = "scale-y"; + shadow-offset-x = "offset-x"; + shadow-offset-y = "offset-y"; + *knobs = { + scale = 0.95; + duration = 0.2; + }; + *placeholders = ((0, "duration"),(1, "scale")); +}; + +slide-out = { + v-timing = { + curve = "cubic-bezier(0.21, 0.02, 0.76, 0.36)"; + start = 0; + duration = "placeholder0"; + end = "window-width * placeholder1 + window-height * placeholder2"; + }; + offset-x = "v-timing * placeholder3"; + offset-y = "v-timing * (1 - placeholder3)"; + shadow-offset-x = "offset-x"; + shadow-offset-y = "offset-y"; + crop-x = "window-x"; + crop-y = "window-y"; + crop-width = "window-width"; + crop-height = "window-height"; + opacity = 1; + blur-opacity = "opacity"; + shadow-opacity = "opacity"; + *knobs = { + duration = 0.2; + direction = (2, ["up", "down", "left", "right"]); + }; + *placeholders = ( + (0, "duration"), + (1, "direction", [0, 0, -1, 1]), + (2, "direction", [-1, 1, 0, 0]), + (3, "direction", [0, 0, 1, 1]), + ); +}; + +slide-in = { + v-timing = { + curve = "cubic-bezier(0.24, 0.64, 0.79, 0.98)"; + start = "window-width * placeholder1 + window-height * placeholder2"; + duration = "placeholder0"; + end = 0; + }; + offset-x = "v-timing * placeholder3"; + offset-y = "v-timing * (1 - placeholder3)"; + shadow-offset-x = "offset-x"; + shadow-offset-y = "offset-y"; + crop-x = "window-x"; + crop-y = "window-y"; + crop-width = "window-width"; + crop-height = "window-height"; + *knobs = { + duration = 0.2; + direction = (2, ["up", "down", "left", "right"]); + }; + *placeholders = ( + (0, "duration"), + (1, "direction", [0, 0, -1, 1]), + (2, "direction", [-1, 1, 0, 0]), + (3, "direction", [0, 0, 1, 1]), + ); +}; + +fly-out = { + v-timing = { + curve = "cubic-bezier(0.05, 0, 0.69, -0.05)"; + duration = "placeholder0"; + start = 0; + end = "(window-height + window-y) * placeholder2 + (window-width + window-x) * placeholder1"; + }; + offset-x = "v-timing * placeholder3"; + offset-y = "v-timing * (1 - placeholder3)"; + shadow-offset-x = "offset-x"; + shadow-offset-y = "offset-y"; + opacity = 1; + shadow-opacity = 1; + blur-opacity = 1; + *knobs = { + duration = 0.2; + direction = (0, ["up", "down", "left", "right"]); + }; + *placeholders = ( + (0, "duration"), + (1, "direction", [0, 0, -1, 1]), + (2, "direction", [-1, 1, 0, 0]), + (3, "direction", [0, 0, 1, 1]), + ); +}; +fly-in = { + v-timing = { + curve = "cubic-bezier(0.17, 0.67, 0.68, 1.03)"; + end = 0; + duration = "placeholder0"; + start = "- window-height - window-y"; + }; + offset-x = "v-timing * placeholder3"; + offset-y = "v-timing * (1 - placeholder3)"; + shadow-offset-x = "offset-x"; + shadow-offset-y = "offset-y"; + opacity = 1; + shadow-opacity = 1; + blur-opacity = 1; + *knobs = { + duration = 0.2; + direction = (0, ["up", "down", "left", "right"]); + }; + *placeholders = ( + (0, "duration"), + (1, "direction", [0, 0, -1, 1]), + (2, "direction", [-1, 1, 0, 0]), + (3, "direction", [0, 0, 1, 1]), + ); +} diff --git a/meson.build b/meson.build index 809a324ded..9b17f0d075 100644 --- a/meson.build +++ b/meson.build @@ -92,6 +92,7 @@ test_h_dep = subproject('test.h').get_variable('test_h_dep') subdir('src') subdir('man') +subdir('tools') install_data('bin/picom-trans', install_dir: get_option('bindir')) install_data('picom.desktop', install_dir: 'share/applications') diff --git a/src/config_libconfig.c b/src/config_libconfig.c index 5e2faaa273..b90f8c0d8a 100644 --- a/src/config_libconfig.c +++ b/src/config_libconfig.c @@ -16,6 +16,7 @@ #include "common.h" #include "config.h" #include "log.h" +#include "transition/preset.h" #include "transition/script.h" #include "utils/dynarr.h" #include "utils/misc.h" @@ -233,8 +234,14 @@ static enum animation_trigger parse_animation_trigger(const char *trigger) { return ANIMATION_TRIGGER_INVALID; } -static struct script * -compile_win_script(config_setting_t *setting, int *output_indices, char **err) { +/// Compile a script from `setting` into `result`, return false on failure. +/// Only the `script` and `output_indices` fields of `result` will be modified. +static bool +compile_win_script(struct win_script *result, config_setting_t *setting, char **err) { + if (config_setting_lookup(setting, "preset")) { + return win_script_parse_preset(result, setting); + } + struct script_output_info outputs[ARR_SIZE(win_script_outputs)]; memcpy(outputs, win_script_outputs, sizeof(win_script_outputs)); @@ -242,20 +249,19 @@ compile_win_script(config_setting_t *setting, int *output_indices, char **err) { .context_info = win_script_context_info, .output_info = outputs, }; - auto script = script_compile(setting, parse_config, err); - if (script == NULL) { - return script; + result->script = script_compile(setting, parse_config, err); + if (result->script == NULL) { + return false; } for (int i = 0; i < NUM_OF_WIN_SCRIPT_OUTPUTS; i++) { - output_indices[i] = outputs[i].slot; + result->output_indices[i] = outputs[i].slot; } - return script; + return true; } static bool set_animation(struct win_script *animations, const enum animation_trigger *triggers, - int number_of_triggers, struct script *script, const int *output_indices, - uint64_t suppressions, unsigned line, bool is_generated) { + int number_of_triggers, struct win_script animation, unsigned line) { bool needed = false; for (int i = 0; i < number_of_triggers; i++) { if (triggers[i] == ANIMATION_TRIGGER_INVALID) { @@ -268,42 +274,39 @@ set_animation(struct win_script *animations, const enum animation_trigger *trigg animation_trigger_names[triggers[i]], line); continue; } - memcpy(animations[triggers[i]].output_indices, output_indices, - sizeof(int[NUM_OF_WIN_SCRIPT_OUTPUTS])); - animations[triggers[i]].script = script; - animations[triggers[i]].suppressions = suppressions; - animations[triggers[i]].is_generated = is_generated; + animations[triggers[i]] = animation; needed = true; } return needed; } -static struct script * -parse_animation_one(struct win_script *animations, config_setting_t *setting) { +static bool parse_animation_one(struct win_script *animations, + struct script ***all_scripts, config_setting_t *setting) { + struct win_script result = {}; auto triggers = config_setting_lookup(setting, "triggers"); if (!triggers) { log_error("Missing triggers in animation script, at line %d", config_setting_source_line(setting)); - return NULL; + return false; } if (!config_setting_is_list(triggers) && !config_setting_is_array(triggers) && config_setting_get_string(triggers) == NULL) { log_error("The \"triggers\" option must either be a string, a list, or " "an array, but is none of those at line %d", config_setting_source_line(triggers)); - return NULL; + return false; } auto number_of_triggers = config_setting_get_string(triggers) == NULL ? config_setting_length(triggers) : 1; if (number_of_triggers > ANIMATION_TRIGGER_LAST) { log_error("Too many triggers in animation defined at line %d", config_setting_source_line(triggers)); - return NULL; + return false; } if (number_of_triggers == 0) { log_error("Trigger list is empty in animation defined at line %d", config_setting_source_line(triggers)); - return NULL; + return false; } enum animation_trigger *trigger_types = alloca(sizeof(enum animation_trigger[number_of_triggers])); @@ -322,7 +325,6 @@ parse_animation_one(struct win_script *animations, config_setting_t *setting) { // script parser shouldn't see this. config_setting_remove(setting, "triggers"); - uint64_t suppressions = 0; auto suppressions_setting = config_setting_lookup(setting, "suppressions"); if (suppressions_setting != NULL) { auto single_suppression = config_setting_get_string(suppressions_setting); @@ -332,16 +334,16 @@ parse_animation_one(struct win_script *animations, config_setting_t *setting) { log_error("The \"suppressions\" option must either be a string, " "a list, or an array, but is none of those at line %d", config_setting_source_line(suppressions_setting)); - return NULL; + return false; } if (single_suppression != NULL) { auto suppression = parse_animation_trigger(single_suppression); if (suppression == ANIMATION_TRIGGER_INVALID) { log_error("Invalid suppression defined at line %d", config_setting_source_line(suppressions_setting)); - return NULL; + return false; } - suppressions = 1 << suppression; + result.suppressions = 1 << suppression; } else { auto len = config_setting_length(suppressions_setting); for (int i = 0; i < len; i++) { @@ -353,39 +355,37 @@ parse_animation_one(struct win_script *animations, config_setting_t *setting) { "contain strings, but one of them is not at " "line %d", config_setting_source_line(suppressions_setting)); - return NULL; + return false; } auto suppression = parse_animation_trigger(suppression_str); if (suppression == ANIMATION_TRIGGER_INVALID) { log_error( "Invalid suppression defined at line %d", config_setting_source_line(suppressions_setting)); - return NULL; + return false; } - suppressions |= 1U << suppression; + result.suppressions |= 1U << suppression; } } config_setting_remove(setting, "suppressions"); } - int output_indices[NUM_OF_WIN_SCRIPT_OUTPUTS]; char *err; - auto script = compile_win_script(setting, output_indices, &err); - if (!script) { + if (!compile_win_script(&result, setting, &err)) { log_error("Failed to parse animation script at line %d: %s", config_setting_source_line(setting), err); free(err); - return NULL; + return false; } - bool needed = set_animation(animations, trigger_types, number_of_triggers, script, - output_indices, suppressions, - config_setting_source_line(setting), false); + bool needed = set_animation(animations, trigger_types, number_of_triggers, result, + config_setting_source_line(setting)); if (!needed) { - script_free(script); - script = NULL; + script_free(result.script); + } else { + dynarr_push(*all_scripts, result.script); } - return script; + return true; } /// `out_scripts`: all the script objects created, this is a dynarr. @@ -394,27 +394,24 @@ static void parse_animations(struct win_script *animations, config_setting_t *se auto number_of_animations = (unsigned)config_setting_length(setting); for (unsigned i = 0; i < number_of_animations; i++) { auto sub = config_setting_get_elem(setting, (unsigned)i); - auto script = parse_animation_one(animations, sub); - if (script != NULL) { - dynarr_push(*out_scripts, script); - } + parse_animation_one(animations, out_scripts, sub); } } #define FADING_TEMPLATE_1 \ "opacity = { " \ - " timing = \"%sms linear\"; " \ + " duration = %s; " \ " start = \"window-raw-opacity-before\"; " \ " end = \"window-raw-opacity\"; " \ "};" \ "shadow-opacity = \"opacity\";" #define FADING_TEMPLATE_2 \ "blur-opacity = { " \ - " timing = \"%sms linear\"; " \ + " duration = %s; " \ " start = %d; end = %d; " \ "};" -static struct script *compile_win_script_from_string(const char *input, int *output_indices) { +static bool compile_win_script_from_string(struct win_script *result, const char *input) { config_t tmp_config; config_setting_t *setting; config_init(&tmp_config); @@ -424,11 +421,11 @@ static struct script *compile_win_script_from_string(const char *input, int *out // Since we are compiling scripts we generated, it can't fail. char *err = NULL; - auto script = compile_win_script(setting, output_indices, &err); + bool succeeded = compile_win_script(result, setting, &err); config_destroy(&tmp_config); BUG_ON(err != NULL); - return script; + return succeeded; } void generate_fading_config(struct options *opt) { @@ -441,8 +438,7 @@ void generate_fading_config(struct options *opt) { unsigned number_of_scripts = 0; int number_of_triggers = 0; - int output_indices[NUM_OF_WIN_SCRIPT_OUTPUTS]; - double duration = 1.0 / opt->fade_in_step * opt->fade_delta; + double duration = 1.0 / opt->fade_in_step * opt->fade_delta / 1000.0; if (!safe_isinf(duration) && !safe_isnan(duration) && duration > 0) { scoped_charp duration_str = NULL; dtostr(duration, &duration_str); @@ -451,7 +447,8 @@ void generate_fading_config(struct options *opt) { asnprintf(&str, &len, FADING_TEMPLATE_1 FADING_TEMPLATE_2, duration_str, duration_str, 0, 1); - auto fade_in1 = compile_win_script_from_string(str, output_indices); + struct win_script fade_in1 = {.is_generated = true}; + BUG_ON(!compile_win_script_from_string(&fade_in1, str)); if (opt->animations[ANIMATION_TRIGGER_OPEN].script == NULL && !opt->no_fading_openclose) { trigger[number_of_triggers++] = ANIMATION_TRIGGER_OPEN; @@ -459,25 +456,24 @@ void generate_fading_config(struct options *opt) { if (opt->animations[ANIMATION_TRIGGER_SHOW].script == NULL) { trigger[number_of_triggers++] = ANIMATION_TRIGGER_SHOW; } - if (set_animation(opt->animations, trigger, number_of_triggers, fade_in1, - output_indices, 0, 0, true)) { - scripts[number_of_scripts++] = fade_in1; + if (set_animation(opt->animations, trigger, number_of_triggers, fade_in1, 0)) { + scripts[number_of_scripts++] = fade_in1.script; } else { - script_free(fade_in1); + script_free(fade_in1.script); } // Fading for opacity change, for these, the blur opacity doesn't change. asnprintf(&str, &len, FADING_TEMPLATE_1, duration_str); - auto fade_in2 = compile_win_script_from_string(str, output_indices); + struct win_script fade_in2 = {.is_generated = true}; + BUG_ON(!compile_win_script_from_string(&fade_in2, str)); number_of_triggers = 0; if (opt->animations[ANIMATION_TRIGGER_INCREASE_OPACITY].script == NULL) { trigger[number_of_triggers++] = ANIMATION_TRIGGER_INCREASE_OPACITY; } - if (set_animation(opt->animations, trigger, number_of_triggers, fade_in2, - output_indices, 0, 0, true)) { - scripts[number_of_scripts++] = fade_in2; + if (set_animation(opt->animations, trigger, number_of_triggers, fade_in2, 0)) { + scripts[number_of_scripts++] = fade_in2.script; } else { - script_free(fade_in2); + script_free(fade_in2.script); } } else { log_error("Invalid fade-in setting (step: %f, delta: %d), ignoring.", @@ -492,7 +488,8 @@ void generate_fading_config(struct options *opt) { // Fading out to nothing, i.e. `hide` and `close` asnprintf(&str, &len, FADING_TEMPLATE_1 FADING_TEMPLATE_2, duration_str, duration_str, 1, 0); - auto fade_out1 = compile_win_script_from_string(str, output_indices); + struct win_script fade_out1 = {.is_generated = true}; + BUG_ON(!compile_win_script_from_string(&fade_out1, str)); number_of_triggers = 0; if (opt->animations[ANIMATION_TRIGGER_CLOSE].script == NULL && !opt->no_fading_openclose) { @@ -501,25 +498,24 @@ void generate_fading_config(struct options *opt) { if (opt->animations[ANIMATION_TRIGGER_HIDE].script == NULL) { trigger[number_of_triggers++] = ANIMATION_TRIGGER_HIDE; } - if (set_animation(opt->animations, trigger, number_of_triggers, fade_out1, - output_indices, 0, 0, true)) { - scripts[number_of_scripts++] = fade_out1; + if (set_animation(opt->animations, trigger, number_of_triggers, fade_out1, 0)) { + scripts[number_of_scripts++] = fade_out1.script; } else { - script_free(fade_out1); + script_free(fade_out1.script); } // Fading for opacity change asnprintf(&str, &len, FADING_TEMPLATE_1, duration_str); - auto fade_out2 = compile_win_script_from_string(str, output_indices); + struct win_script fade_out2 = {.is_generated = true}; + BUG_ON(!compile_win_script_from_string(&fade_out2, str)); number_of_triggers = 0; if (opt->animations[ANIMATION_TRIGGER_DECREASE_OPACITY].script == NULL) { trigger[number_of_triggers++] = ANIMATION_TRIGGER_DECREASE_OPACITY; } - if (set_animation(opt->animations, trigger, number_of_triggers, fade_out2, - output_indices, 0, 0, true)) { - scripts[number_of_scripts++] = fade_out2; + if (set_animation(opt->animations, trigger, number_of_triggers, fade_out2, 0)) { + scripts[number_of_scripts++] = fade_out2.script; } else { - script_free(fade_out2); + script_free(fade_out2.script); } } else { log_error("Invalid fade-out setting (step: %f, delta: %d), ignoring.", diff --git a/src/meson.build b/src/meson.build index ed8784306d..2a0419f393 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,27 +2,52 @@ libev = dependency('libev', required: false) if not libev.found() libev = cc.find_library('ev') endif -base_deps = [ - cc.find_library('m'), - libev +base_deps = [cc.find_library('m'), libev] + +srcs = [ + files( + 'api.c', + 'atom.c', + 'c2.c', + 'config.c', + 'config_libconfig.c', + 'diagnostic.c', + 'event.c', + 'inspect.c', + 'log.c', + 'options.c', + 'picom.c', + 'render.c', + 'vblank.c', + 'vsync.c', + 'x.c', + ), ] - -srcs = [ files('picom.c', 'c2.c', 'x.c', 'config.c', 'vsync.c', - 'diagnostic.c', 'render.c', 'log.c', - 'options.c', 'event.c', 'atom.c', - 'vblank.c','config_libconfig.c', 'inspect.c', 'api.c') ] picom_inc = include_directories(['.', '../include']) cflags = [] required_xcb_packages = [ - 'xcb', 'xcb-composite', 'xcb-damage', 'xcb-glx', 'xcb-present', 'xcb-randr', - 'xcb-render', 'xcb-shape', 'xcb-sync', 'xcb-xfixes' + 'xcb', + 'xcb-composite', + 'xcb-damage', + 'xcb-glx', + 'xcb-present', + 'xcb-randr', + 'xcb-render', + 'xcb-shape', + 'xcb-sync', + 'xcb-xfixes', ] # Some XCB packages are here because their versioning differs (see check below). required_packages = [ - 'pixman-1', 'x11', 'x11-xcb', 'xcb-image', 'xcb-renderutil', 'xcb-util', + 'pixman-1', + 'x11', + 'x11-xcb', + 'xcb-image', + 'xcb-renderutil', + 'xcb-util', 'threads', ] @@ -41,9 +66,11 @@ if not libconfig_dep.found() cmake = import('cmake') sub_libconfig_opts = cmake.subproject_options() - sub_libconfig_opts.add_cmake_defines({ - 'BUILD_SHARED_LIBS': false, - }) + sub_libconfig_opts.add_cmake_defines( + { + 'BUILD_SHARED_LIBS': false, + }, + ) sub_libconfig_opts.set_install(false) sub_libconfig = cmake.subproject('libconfig', options: sub_libconfig_opts) base_deps += [sub_libconfig.dependency('config')] @@ -52,7 +79,7 @@ else endif if not cc.has_header('uthash.h') - error('Dependency uthash not found') + error('Dependency uthash not found') endif deps = [] @@ -71,18 +98,18 @@ endif if get_option('opengl') cflags += ['-DCONFIG_OPENGL'] deps += [dependency('epoxy', required: true)] - srcs += [ 'opengl.c' ] + srcs += ['opengl.c'] endif if get_option('dbus') cflags += ['-DCONFIG_DBUS'] deps += [dependency('dbus-1', required: true)] - srcs += [ 'dbus.c', 'rtkit.c' ] + srcs += ['dbus.c', 'rtkit.c'] endif if get_option('xrescheck') cflags += ['-DDEBUG_XRC'] - srcs += [ 'xrescheck.c' ] + srcs += ['xrescheck.c'] endif if get_option('unittest') @@ -92,8 +119,12 @@ endif host_system = host_machine.system() if host_system == 'linux' cflags += ['-DHAS_INOTIFY'] -elif (host_system == 'freebsd' or host_system == 'netbsd' or - host_system == 'dragonfly' or host_system == 'openbsd') +elif ( + host_system == 'freebsd' + or host_system == 'netbsd' + or host_system == 'dragonfly' + or host_system == 'openbsd' +) cflags += ['-DHAS_KQUEUE'] endif @@ -105,26 +136,51 @@ subdir('utils') dl_dep = [] if not cc.has_function('dlopen') - dl_dep = [ cc.find_library('dl', required: true) ] + dl_dep = [cc.find_library('dl', required: true)] endif -picom = executable('picom', srcs, c_args: cflags, - dependencies: [ base_deps, deps, test_h_dep ] + dl_dep, - install: true, include_directories: picom_inc, - export_dynamic: true, gnu_symbol_visibility: 'hidden') +libtools = static_library( + 'libtools', + [ + 'log.c', + 'utils/dynarr.c', + 'utils/misc.c', + 'utils/str.c', + 'transition/curve.c', + 'transition/script.c', + ], + include_directories: picom_inc, + dependencies: [test_h_dep], + install: false, + build_by_default: false, +) + +picom = executable( + 'picom', + srcs, + c_args: cflags, + dependencies: [base_deps, deps, test_h_dep] + dl_dep, + install: true, + include_directories: picom_inc, + export_dynamic: true, + gnu_symbol_visibility: 'hidden', +) if get_option('unittest') - test('picom unittest', picom, args: [ '--unittest' ]) + test('picom unittest', picom, args: ['--unittest']) endif install_symlink('picom-inspect', install_dir: 'bin', pointing_to: 'picom') if cc.has_argument('-fsanitize=fuzzer') - c2_fuzz = executable('c2_fuzz', srcs + ['fuzzer/c2.c'], - c_args: cflags + ['-fsanitize=fuzzer', '-DCONFIG_FUZZER'], - link_args: ['-fsanitize=fuzzer'], - dependencies: [ base_deps, deps, test_h_dep ], - build_by_default: false, - install: false, include_directories: picom_inc - ) + c2_fuzz = executable( + 'c2_fuzz', + srcs + ['fuzzer/c2.c'], + c_args: cflags + ['-fsanitize=fuzzer', '-DCONFIG_FUZZER'], + link_args: ['-fsanitize=fuzzer'], + dependencies: [base_deps, deps, test_h_dep], + build_by_default: false, + install: false, + include_directories: picom_inc, + ) endif diff --git a/src/transition/curve.c b/src/transition/curve.c index 812e87d564..28f12e6eba 100644 --- a/src/transition/curve.c +++ b/src/transition/curve.c @@ -15,46 +15,30 @@ static double curve_sample_linear(const struct curve *this attr_unused, double p return progress; } -static void noop_free(const struct curve *this attr_unused) { +static char *curve_linear_to_c(const struct curve * /*this*/) { + return strdup("{.type = CURVE_LINEAR},"); } -static void trivial_free(const struct curve *this) { - free((void *)this); -} - -static const struct curve static_linear_curve = { - .sample = curve_sample_linear, - .free = noop_free, -}; -const struct curve *curve_new_linear(void) { - return &static_linear_curve; -} - -/// Cubic bezier interpolator. -/// -/// Stolen from servo: -/// https://searchfox.org/mozilla-central/rev/5da2d56d12/servo/components/style/bezier.rs -struct cubic_bezier_curve { - struct curve base; - double ax, bx, cx; - double ay, by, cy; -}; +// Cubic bezier interpolator. +// +// Stolen from servo: +// https://searchfox.org/mozilla-central/rev/5da2d56d12/servo/components/style/bezier.rs -static inline double cubic_bezier_sample_x(const struct cubic_bezier_curve *self, double t) { +static inline double cubic_bezier_sample_x(const struct curve_cubic_bezier *self, double t) { return ((self->ax * t + self->bx) * t + self->cx) * t; } -static inline double cubic_bezier_sample_y(const struct cubic_bezier_curve *self, double t) { +static inline double cubic_bezier_sample_y(const struct curve_cubic_bezier *self, double t) { return ((self->ay * t + self->by) * t + self->cy) * t; } static inline double -cubic_bezier_sample_derivative_x(const struct cubic_bezier_curve *self, double t) { +cubic_bezier_sample_derivative_x(const struct curve_cubic_bezier *self, double t) { return (3.0 * self->ax * t + 2.0 * self->bx) * t + self->cx; } // Solve for the `t` in cubic bezier function that corresponds to `x` -static inline double cubic_bezier_solve_x(const struct cubic_bezier_curve *this, double x) { +static inline double cubic_bezier_solve_x(const struct curve_cubic_bezier *this, double x) { static const int NEWTON_METHOD_ITERATIONS = 8; double t = x; // Fast path: try Newton's method. @@ -88,47 +72,26 @@ static inline double cubic_bezier_solve_x(const struct cubic_bezier_curve *this, return t; } -static double curve_sample_cubic_bezier(const struct curve *base, double progress) { - auto this = (struct cubic_bezier_curve *)base; +static double +curve_sample_cubic_bezier(const struct curve_cubic_bezier *curve, double progress) { assert(progress >= 0 && progress <= 1); if (progress == 0 || progress == 1) { return progress; } - double t = cubic_bezier_solve_x(this, progress); - return cubic_bezier_sample_y(this, t); + double t = cubic_bezier_solve_x(curve, progress); + return cubic_bezier_sample_y(curve, t); } -const struct curve *curve_new_cubic_bezier(double x1, double y1, double x2, double y2) { - if (x1 == y1 && x2 == y2) { - return curve_new_linear(); - } - - assert(x1 >= 0 && x1 <= 1 && x2 >= 0 && x2 <= 1); - auto ret = ccalloc(1, struct cubic_bezier_curve); - ret->base.sample = curve_sample_cubic_bezier; - ret->base.free = trivial_free; - - double cx = 3. * x1; - double bx = 3. * (x2 - x1) - cx; - double cy = 3. * y1; - double by = 3. * (y2 - y1) - cy; - ret->ax = 1. - cx - bx; - ret->bx = bx; - ret->cx = cx; - ret->ay = 1. - cy - by; - ret->by = by; - ret->cy = cy; - return &ret->base; -} - -struct step_curve { - struct curve base; - int steps; - bool jump_start, jump_end; -}; +static char *curve_cubic_bezier_to_c(const struct curve_cubic_bezier *curve) { + char *buf = NULL; + casprintf(&buf, + "{.type = CURVE_CUBIC_BEZIER, .bezier = { .ax = %a, .bx = %a, " + ".cx = %a, .ay = %a, .by = %a, .cy = %a }},", + curve->ax, curve->bx, curve->cx, curve->ay, curve->by, curve->cy); + return buf; +} -static double curve_sample_step(const struct curve *base, double progress) { - auto this = (struct step_curve *)base; +static double curve_sample_step(const struct curve_step *this, double progress) { double y_steps = this->steps - 1 + this->jump_end + this->jump_start, x_steps = this->steps; if (progress == 1) { @@ -143,29 +106,28 @@ static double curve_sample_step(const struct curve *base, double progress) { return quantized / y_steps; } -const struct curve *curve_new_step(int steps, bool jump_start, bool jump_end) { - assert(steps > 0); - auto ret = ccalloc(1, struct step_curve); - ret->base.sample = curve_sample_step; - ret->base.free = trivial_free; - ret->steps = steps; - ret->jump_start = jump_start; - ret->jump_end = jump_end; - return &ret->base; +static char *curve_step_to_c(const struct curve_step *this) { + char *buf = NULL; + casprintf(&buf, + "{.type = CURVE_STEP, .step = { .steps = %d, .jump_start = %s, " + ".jump_end = %s }},", + this->steps, this->jump_start ? "true" : "false", + this->jump_end ? "true" : "false"); + return buf; } -const struct curve *parse_linear(const char *str, const char **end, char **err) { +struct curve parse_linear(const char *str, const char **end, char **err) { *end = str; *err = NULL; - return &static_linear_curve; + return CURVE_LINEAR_INIT; } -const struct curve *parse_steps(const char *input_str, const char **out_end, char **err) { +struct curve parse_steps(const char *input_str, const char **out_end, char **err) { const char *str = input_str; *err = NULL; if (*str != '(') { asprintf(err, "Invalid steps %s.", str); - return NULL; + return CURVE_INVALID_INIT; } str += 1; str = skip_space(str); @@ -173,12 +135,12 @@ const struct curve *parse_steps(const char *input_str, const char **out_end, cha auto steps = strtol(str, &end, 10); if (end == str || steps > INT_MAX) { asprintf(err, "Invalid step count at \"%s\".", str); - return NULL; + return CURVE_INVALID_INIT; } str = skip_space(end); if (*str != ',') { asprintf(err, "Invalid steps argument list \"%s\".", input_str); - return NULL; + return CURVE_INVALID_INIT; } str = skip_space(str + 1); bool jump_start = @@ -187,25 +149,24 @@ const struct curve *parse_steps(const char *input_str, const char **out_end, cha starts_with(str, "jump-end", true) || starts_with(str, "jump-both", true); if (!jump_start && !jump_end && !starts_with(str, "jump-none", true)) { asprintf(err, "Invalid jump setting for steps \"%s\".", str); - return NULL; + return CURVE_INVALID_INIT; } str += jump_start ? (jump_end ? 9 : 10) : (jump_end ? 8 : 9); str = skip_space(str); if (*str != ')') { asprintf(err, "Invalid steps argument list \"%s\".", input_str); - return NULL; + return CURVE_INVALID_INIT; } *out_end = str + 1; return curve_new_step((int)steps, jump_start, jump_end); } -const struct curve * -parse_cubic_bezier(const char *input_str, const char **out_end, char **err) { +struct curve parse_cubic_bezier(const char *input_str, const char **out_end, char **err) { double numbers[4]; const char *str = input_str; if (*str != '(') { asprintf(err, "Invalid cubic-bazier %s.", str); - return NULL; + return CURVE_INVALID_INIT; } str += 1; for (int i = 0; i < 4; i++) { @@ -215,13 +176,13 @@ parse_cubic_bezier(const char *input_str, const char **out_end, char **err) { numbers[i] = strtod_simple(str, &end); if (end == str) { asprintf(err, "Invalid number %s.", str); - return NULL; + return CURVE_INVALID_INIT; } str = skip_space(end); const char expected = i == 3 ? ')' : ','; if (*str != expected) { asprintf(err, "Invalid cubic-bazier argument list %s.", input_str); - return NULL; + return CURVE_INVALID_INIT; } str += 1; } @@ -229,7 +190,7 @@ parse_cubic_bezier(const char *input_str, const char **out_end, char **err) { return curve_new_cubic_bezier(numbers[0], numbers[1], numbers[2], numbers[3]); } -typedef const struct curve *(*curve_parser)(const char *str, const char **end, char **err); +typedef struct curve (*curve_parser)(const char *str, const char **end, char **err); static const struct { curve_parser parse; @@ -240,7 +201,7 @@ static const struct { {parse_steps, "steps"}, }; -const struct curve *curve_parse(const char *str, const char **end, char **err) { +struct curve curve_parse(const char *str, const char **end, char **err) { str = skip_space(str); for (size_t i = 0; i < ARR_SIZE(curve_parsers); i++) { auto name_len = strlen(curve_parsers[i].name); @@ -249,5 +210,26 @@ const struct curve *curve_parse(const char *str, const char **end, char **err) { } } asprintf(err, "Unknown curve type \"%s\".", str); - return NULL; + return CURVE_INVALID_INIT; +} + +double curve_sample(const struct curve *curve, double progress) { + switch (curve->type) { + case CURVE_LINEAR: return curve_sample_linear(curve, progress); + case CURVE_STEP: return curve_sample_step(&curve->step, progress); + case CURVE_CUBIC_BEZIER: + return curve_sample_cubic_bezier(&curve->bezier, progress); + case CURVE_INVALID: + default: unreachable(); + } +} + +char *curve_to_c(const struct curve *curve) { + switch (curve->type) { + case CURVE_LINEAR: return curve_linear_to_c(curve); + case CURVE_STEP: return curve_step_to_c(&curve->step); + case CURVE_CUBIC_BEZIER: return curve_cubic_bezier_to_c(&curve->bezier); + case CURVE_INVALID: + default: unreachable(); + } } diff --git a/src/transition/curve.h b/src/transition/curve.h index 72b2de53ac..b0c58aca3d 100644 --- a/src/transition/curve.h +++ b/src/transition/curve.h @@ -2,20 +2,51 @@ // Copyright (c) Yuxuan Shui #pragma once +#include #include -// ========================== Interpolators ========================== +enum curve_type { + CURVE_LINEAR, + CURVE_CUBIC_BEZIER, + CURVE_STEP, + CURVE_INVALID, +}; struct curve { - /// The interpolator function for an animatable. This function should calculate - /// the current value of the `animatable` based on its `start`, `target`, - /// `duration` and `progress`. - double (*sample)(const struct curve *this, double progress); - /// Free the interpolator. - void (*free)(const struct curve *this); + enum curve_type type; + union { + struct curve_cubic_bezier { + double ax, bx, cx; + double ay, by, cy; + } bezier; + struct curve_step { + int steps; + bool jump_start, jump_end; + } step; + }; }; -const struct curve *curve_new_linear(void); -const struct curve *curve_new_cubic_bezier(double x1, double y1, double x2, double y2); -const struct curve *curve_new_step(int steps, bool jump_start, bool jump_end); -const struct curve *curve_parse(const char *str, const char **end, char **err); +static const struct curve CURVE_LINEAR_INIT = {.type = CURVE_LINEAR}; +static const struct curve CURVE_INVALID_INIT = {.type = CURVE_INVALID}; + +static inline struct curve curve_new_cubic_bezier(double x1, double y1, double x2, double y2) { + double cx = 3. * x1; + double bx = 3. * (x2 - x1) - cx; + double cy = 3. * y1; + double by = 3. * (y2 - y1) - cy; + return (struct curve){ + .type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = 1. - cx - bx, .bx = bx, .cx = cx, .ay = 1. - cy - by, .by = by, .cy = cy}, + }; +} +static inline struct curve curve_new_step(int steps, bool jump_start, bool jump_end) { + assert(steps > 0); + return (struct curve){ + .type = CURVE_STEP, + .step = {.steps = steps, .jump_start = jump_start, .jump_end = jump_end}, + }; +} +struct curve curve_parse(const char *str, const char **end, char **err); +/// Calculate the value of the curve at `progress`. +double curve_sample(const struct curve *curve, double progress); +char *curve_to_c(const struct curve *curve); diff --git a/src/transition/generated/script_templates.c b/src/transition/generated/script_templates.c new file mode 100644 index 0000000000..25aff2c1ec --- /dev/null +++ b/src/transition/generated/script_templates.c @@ -0,0 +1,1265 @@ +// This file is generated by tools/animgen.c from data/animation_presets.conf +// This file is included in git repository for convenience only. +// DO NOT EDIT THIS FILE! + +#include +#include "../curve.h" +#include "../script.h" +#include "../script_internal.h" +#include "config.h" +#include "utils/misc.h" +static struct script *script_template__disappear(int *output_slots) { + static const struct instruction instrs[] = { + {.type = INST_BRANCH_ONCE, .rel = 59}, + {.type = INST_LOAD, .slot = 14}, + {.type = INST_LOAD, .slot = 13}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 15}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_LINEAR}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 13}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 0}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_STORE, .slot = 1}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_STORE, .slot = 2}, + {.type = INST_LOAD, .slot = 17}, + {.type = INST_LOAD, .slot = 16}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 18}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = -0x1.4cccccccccccep-1, + .bx = 0x1.051eb851eb852p+0, + .cx = 0x1.428f5c28f5c2ap-1, + .ay = -0x1.47ae147ae148p-6, + .by = 0x1.eb851eb851eb8p-1, + .cy = 0x1.eb851eb851ebap-5}}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 16}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 5}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD, .slot = 5}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_IMM, .imm = 0x1p+1}, + {.type = INST_OP, .op = OP_DIV}, + {.type = INST_LOAD_CTX, .ctx = 16}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 3}, + {.type = INST_LOAD, .slot = 5}, + {.type = INST_STORE, .slot = 6}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD, .slot = 6}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_IMM, .imm = 0x1p+1}, + {.type = INST_OP, .op = OP_DIV}, + {.type = INST_LOAD_CTX, .ctx = 24}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 4}, + {.type = INST_LOAD, .slot = 5}, + {.type = INST_STORE, .slot = 7}, + {.type = INST_LOAD, .slot = 6}, + {.type = INST_STORE, .slot = 8}, + {.type = INST_LOAD, .slot = 3}, + {.type = INST_STORE, .slot = 9}, + {.type = INST_LOAD, .slot = 4}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_BRANCH_ONCE, .rel = 15}, + {.type = INST_HALT}, + {.type = INST_LOAD_CTX, .ctx = 32}, + {.type = INST_STORE_OVER_NAN, .slot = 13}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 15}, + {.type = INST_LOAD_CTX, .ctx = 40}, + {.type = INST_STORE, .slot = 14}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_STORE_OVER_NAN, .slot = 16}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 18}, + {.type = INST_LOAD_CTX, .ctx = 1073741828}, + {.type = INST_STORE, .slot = 17}, + {.type = INST_BRANCH, .rel = -70}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_STORE, .slot = 12}, + {.type = INST_LOAD, .slot = 15}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 12}, + {.type = INST_LOAD, .slot = 18}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 12}, + {.type = INST_HALT}, + }; + struct script *ret = malloc(offsetof(struct script, instrs) + sizeof(instrs)); + ret->len = ARR_SIZE(instrs); + ret->elapsed_slot = 11; + ret->n_slots = 19; + ret->stack_size = 3; + ret->vars = NULL; + ret->overrides = NULL; + memcpy(ret->instrs, instrs, sizeof(instrs)); + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("opacity"), .slot = 0, .index = 0}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("blur-opacity"), .slot = 1, .index = 1}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-opacity"), .slot = 2, .index = 2}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-x"), .slot = 3, .index = 3}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-y"), .slot = 4, .index = 4}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("scale-x"), .slot = 5, .index = 5}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("scale-y"), .slot = 6, .index = 6}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-scale-x"), .slot = 7, .index = 7}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-scale-y"), .slot = 8, .index = 8}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-x"), .slot = 9, .index = 9}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-y"), .slot = 10, .index = 10}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("opacity"), .slot = 13}; + HASH_ADD_STR(ret->overrides, name, override); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("scale-x"), .slot = 16}; + HASH_ADD_STR(ret->overrides, name, override); + } + output_slots[0] = 3; + output_slots[1] = 4; + output_slots[2] = 9; + output_slots[3] = 10; + output_slots[4] = 0; + output_slots[5] = 1; + output_slots[6] = 2; + output_slots[7] = 5; + output_slots[8] = 6; + output_slots[9] = 7; + output_slots[10] = 8; + output_slots[11] = -1; + output_slots[12] = -1; + output_slots[13] = -1; + output_slots[14] = -1; + return ret; +} + +static bool +win_script_preset__disappear(struct win_script *output, config_setting_t *setting) { + output->script = script_template__disappear(output->output_indices); + double knob_duration = 0x1.999999999999ap-3; + config_setting_lookup_float(setting, "duration", &knob_duration); + double knob_scale = 0x1.e666666666666p-1; + config_setting_lookup_float(setting, "scale", &knob_scale); + struct script_specialization_context spec[] = { + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 0, .value = knob_duration}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 4, .value = knob_scale}, + }; + script_specialize(output->script, spec, ARR_SIZE(spec)); + return true; +} +static struct script *script_template__appear(int *output_slots) { + static const struct instruction instrs[] = { + {.type = INST_BRANCH_ONCE, .rel = 59}, + {.type = INST_LOAD, .slot = 14}, + {.type = INST_LOAD, .slot = 13}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 15}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_LINEAR}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 13}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 0}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_STORE, .slot = 1}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_STORE, .slot = 2}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD, .slot = 16}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 17}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = -0x1.4cccccccccccep-1, + .bx = 0x1.dc28f5c28f5c3p-1, + .cx = 0x1.70a3d70a3d70bp-1, + .ay = -0x1.47ae147ae14p-6, + .by = -0x1.cccccccccccd4p-1, + .cy = 0x1.eb851eb851ebap+0}}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 16}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 5}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD, .slot = 5}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_IMM, .imm = 0x1p+1}, + {.type = INST_OP, .op = OP_DIV}, + {.type = INST_LOAD_CTX, .ctx = 16}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 3}, + {.type = INST_LOAD, .slot = 5}, + {.type = INST_STORE, .slot = 6}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD, .slot = 6}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_IMM, .imm = 0x1p+1}, + {.type = INST_OP, .op = OP_DIV}, + {.type = INST_LOAD_CTX, .ctx = 24}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 4}, + {.type = INST_LOAD, .slot = 5}, + {.type = INST_STORE, .slot = 7}, + {.type = INST_LOAD, .slot = 6}, + {.type = INST_STORE, .slot = 8}, + {.type = INST_LOAD, .slot = 3}, + {.type = INST_STORE, .slot = 9}, + {.type = INST_LOAD, .slot = 4}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_BRANCH_ONCE, .rel = 13}, + {.type = INST_HALT}, + {.type = INST_LOAD_CTX, .ctx = 32}, + {.type = INST_STORE_OVER_NAN, .slot = 13}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 15}, + {.type = INST_LOAD_CTX, .ctx = 40}, + {.type = INST_STORE, .slot = 14}, + {.type = INST_LOAD_CTX, .ctx = 1073741828}, + {.type = INST_STORE_OVER_NAN, .slot = 16}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 17}, + {.type = INST_BRANCH, .rel = -68}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_STORE, .slot = 12}, + {.type = INST_LOAD, .slot = 15}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 12}, + {.type = INST_LOAD, .slot = 17}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 12}, + {.type = INST_HALT}, + }; + struct script *ret = malloc(offsetof(struct script, instrs) + sizeof(instrs)); + ret->len = ARR_SIZE(instrs); + ret->elapsed_slot = 11; + ret->n_slots = 18; + ret->stack_size = 3; + ret->vars = NULL; + ret->overrides = NULL; + memcpy(ret->instrs, instrs, sizeof(instrs)); + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("opacity"), .slot = 0, .index = 0}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("blur-opacity"), .slot = 1, .index = 1}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-opacity"), .slot = 2, .index = 2}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-x"), .slot = 3, .index = 3}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-y"), .slot = 4, .index = 4}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("scale-x"), .slot = 5, .index = 5}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("scale-y"), .slot = 6, .index = 6}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-scale-x"), .slot = 7, .index = 7}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-scale-y"), .slot = 8, .index = 8}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-x"), .slot = 9, .index = 9}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-y"), .slot = 10, .index = 10}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("opacity"), .slot = 13}; + HASH_ADD_STR(ret->overrides, name, override); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("scale-x"), .slot = 16}; + HASH_ADD_STR(ret->overrides, name, override); + } + output_slots[0] = 3; + output_slots[1] = 4; + output_slots[2] = 9; + output_slots[3] = 10; + output_slots[4] = 0; + output_slots[5] = 1; + output_slots[6] = 2; + output_slots[7] = 5; + output_slots[8] = 6; + output_slots[9] = 7; + output_slots[10] = 8; + output_slots[11] = -1; + output_slots[12] = -1; + output_slots[13] = -1; + output_slots[14] = -1; + return ret; +} + +static bool win_script_preset__appear(struct win_script *output, config_setting_t *setting) { + output->script = script_template__appear(output->output_indices); + double knob_duration = 0x1.999999999999ap-3; + config_setting_lookup_float(setting, "duration", &knob_duration); + double knob_scale = 0x1.e666666666666p-1; + config_setting_lookup_float(setting, "scale", &knob_scale); + struct script_specialization_context spec[] = { + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 0, .value = knob_duration}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 4, .value = knob_scale}, + }; + script_specialize(output->script, spec, ARR_SIZE(spec)); + return true; +} +static struct script *script_template__slide_out(int *output_slots) { + static const struct instruction instrs[] = { + {.type = INST_BRANCH_ONCE, .rel = 42}, + {.type = INST_LOAD, .slot = 15}, + {.type = INST_LOAD, .slot = 14}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 16}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = -0x1.4cccccccccccep-1, + .bx = 0x1.051eb851eb852p+0, + .cx = 0x1.428f5c28f5c2ap-1, + .ay = -0x1.47ae147ae148p-6, + .by = 0x1.eb851eb851eb8p-1, + .cy = 0x1.eb851eb851ebap-5}}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 14}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 0}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_LOAD_CTX, .ctx = 1073741836}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 1}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD_CTX, .ctx = 1073741836}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 2}, + {.type = INST_LOAD, .slot = 1}, + {.type = INST_STORE, .slot = 3}, + {.type = INST_LOAD, .slot = 2}, + {.type = INST_STORE, .slot = 4}, + {.type = INST_LOAD_CTX, .ctx = 0}, + {.type = INST_STORE, .slot = 5}, + {.type = INST_LOAD_CTX, .ctx = 8}, + {.type = INST_STORE, .slot = 6}, + {.type = INST_LOAD_CTX, .ctx = 16}, + {.type = INST_STORE, .slot = 7}, + {.type = INST_LOAD_CTX, .ctx = 24}, + {.type = INST_STORE, .slot = 8}, + {.type = INST_LOAD, .slot = 9}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_LOAD, .slot = 9}, + {.type = INST_STORE, .slot = 11}, + {.type = INST_BRANCH_ONCE, .rel = 17}, + {.type = INST_HALT}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_STORE_OVER_NAN, .slot = 14}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 16}, + {.type = INST_LOAD_CTX, .ctx = 16}, + {.type = INST_LOAD_CTX, .ctx = 1073741828}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD_CTX, .ctx = 24}, + {.type = INST_LOAD_CTX, .ctx = 1073741832}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 15}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_STORE, .slot = 9}, + {.type = INST_BRANCH, .rel = -55}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_STORE, .slot = 13}, + {.type = INST_LOAD, .slot = 16}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 13}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 13}, + {.type = INST_HALT}, + }; + struct script *ret = malloc(offsetof(struct script, instrs) + sizeof(instrs)); + ret->len = ARR_SIZE(instrs); + ret->elapsed_slot = 12; + ret->n_slots = 17; + ret->stack_size = 3; + ret->vars = NULL; + ret->overrides = NULL; + memcpy(ret->instrs, instrs, sizeof(instrs)); + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("v-timing"), .slot = 0, .index = 0}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-x"), .slot = 1, .index = 1}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-y"), .slot = 2, .index = 2}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-x"), .slot = 3, .index = 3}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-y"), .slot = 4, .index = 4}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("crop-x"), .slot = 5, .index = 5}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("crop-y"), .slot = 6, .index = 6}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("crop-width"), .slot = 7, .index = 7}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("crop-height"), .slot = 8, .index = 8}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("opacity"), .slot = 9, .index = 9}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("blur-opacity"), .slot = 10, .index = 10}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-opacity"), .slot = 11, .index = 11}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("v-timing"), .slot = 14}; + HASH_ADD_STR(ret->overrides, name, override); + } + output_slots[0] = 1; + output_slots[1] = 2; + output_slots[2] = 3; + output_slots[3] = 4; + output_slots[4] = 9; + output_slots[5] = 10; + output_slots[6] = 11; + output_slots[7] = -1; + output_slots[8] = -1; + output_slots[9] = -1; + output_slots[10] = -1; + output_slots[11] = 5; + output_slots[12] = 6; + output_slots[13] = 7; + output_slots[14] = 8; + return ret; +} + +static bool +win_script_preset__slide_out(struct win_script *output, config_setting_t *setting) { + output->script = script_template__slide_out(output->output_indices); + double knob_duration = 0x1.999999999999ap-3; + config_setting_lookup_float(setting, "duration", &knob_duration); + const char *knob_direction = "left"; + config_setting_lookup_string(setting, "direction", &knob_direction); + double placeholder1_direction; + double placeholder2_direction; + double placeholder3_direction; + if (strcmp(knob_direction, "up") == 0) { + placeholder1_direction = 0x0p+0; + placeholder2_direction = -0x1p+0; + placeholder3_direction = 0x0p+0; + } else if (strcmp(knob_direction, "down") == 0) { + placeholder1_direction = 0x0p+0; + placeholder2_direction = 0x1p+0; + placeholder3_direction = 0x0p+0; + } else if (strcmp(knob_direction, "left") == 0) { + placeholder1_direction = -0x1p+0; + placeholder2_direction = 0x0p+0; + placeholder3_direction = 0x1p+0; + } else if (strcmp(knob_direction, "right") == 0) { + placeholder1_direction = 0x1p+0; + placeholder2_direction = 0x0p+0; + placeholder3_direction = 0x1p+0; + } else { + log_error("Invalid choice \"%s\" for option \"direction\". Line %d.", + knob_direction, + config_setting_source_line( + config_setting_get_member(setting, "direction"))); + log_error(" Valid ones are: \"up\", \"down\", \"left\", \"right\""); + script_free(output->script); + output->script = NULL; + return false; + } + struct script_specialization_context spec[] = { + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 0, .value = knob_duration}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 4, .value = placeholder1_direction}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 8, .value = placeholder2_direction}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 12, .value = placeholder3_direction}, + }; + script_specialize(output->script, spec, ARR_SIZE(spec)); + return true; +} +static struct script *script_template__slide_in(int *output_slots) { + static const struct instruction instrs[] = { + {.type = INST_BRANCH_ONCE, .rel = 38}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 9}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = -0x1.4cccccccccccep-1, + .bx = 0x1.dc28f5c28f5c3p-1, + .cx = 0x1.70a3d70a3d70bp-1, + .ay = -0x1.47ae147ae14p-6, + .by = -0x1.cccccccccccd4p-1, + .cy = 0x1.eb851eb851ebap+0}}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 0}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_LOAD_CTX, .ctx = 1073741836}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 1}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD_CTX, .ctx = 1073741836}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 2}, + {.type = INST_LOAD, .slot = 1}, + {.type = INST_STORE, .slot = 3}, + {.type = INST_LOAD, .slot = 2}, + {.type = INST_STORE, .slot = 4}, + {.type = INST_LOAD_CTX, .ctx = 0}, + {.type = INST_STORE, .slot = 5}, + {.type = INST_LOAD_CTX, .ctx = 8}, + {.type = INST_STORE, .slot = 6}, + {.type = INST_LOAD_CTX, .ctx = 16}, + {.type = INST_STORE, .slot = 7}, + {.type = INST_LOAD_CTX, .ctx = 24}, + {.type = INST_STORE, .slot = 8}, + {.type = INST_BRANCH_ONCE, .rel = 13}, + {.type = INST_HALT}, + {.type = INST_LOAD_CTX, .ctx = 16}, + {.type = INST_LOAD_CTX, .ctx = 1073741828}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD_CTX, .ctx = 24}, + {.type = INST_LOAD_CTX, .ctx = 1073741832}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE_OVER_NAN, .slot = 11}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 12}, + {.type = INST_BRANCH, .rel = -47}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 10}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 10}, + {.type = INST_HALT}, + }; + struct script *ret = malloc(offsetof(struct script, instrs) + sizeof(instrs)); + ret->len = ARR_SIZE(instrs); + ret->elapsed_slot = 9; + ret->n_slots = 13; + ret->stack_size = 3; + ret->vars = NULL; + ret->overrides = NULL; + memcpy(ret->instrs, instrs, sizeof(instrs)); + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("v-timing"), .slot = 0, .index = 0}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-x"), .slot = 1, .index = 1}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-y"), .slot = 2, .index = 2}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-x"), .slot = 3, .index = 3}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-y"), .slot = 4, .index = 4}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("crop-x"), .slot = 5, .index = 5}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("crop-y"), .slot = 6, .index = 6}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("crop-width"), .slot = 7, .index = 7}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("crop-height"), .slot = 8, .index = 8}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("v-timing"), .slot = 11}; + HASH_ADD_STR(ret->overrides, name, override); + } + output_slots[0] = 1; + output_slots[1] = 2; + output_slots[2] = 3; + output_slots[3] = 4; + output_slots[4] = -1; + output_slots[5] = -1; + output_slots[6] = -1; + output_slots[7] = -1; + output_slots[8] = -1; + output_slots[9] = -1; + output_slots[10] = -1; + output_slots[11] = 5; + output_slots[12] = 6; + output_slots[13] = 7; + output_slots[14] = 8; + return ret; +} + +static bool win_script_preset__slide_in(struct win_script *output, config_setting_t *setting) { + output->script = script_template__slide_in(output->output_indices); + double knob_duration = 0x1.999999999999ap-3; + config_setting_lookup_float(setting, "duration", &knob_duration); + const char *knob_direction = "left"; + config_setting_lookup_string(setting, "direction", &knob_direction); + double placeholder1_direction; + double placeholder2_direction; + double placeholder3_direction; + if (strcmp(knob_direction, "up") == 0) { + placeholder1_direction = 0x0p+0; + placeholder2_direction = -0x1p+0; + placeholder3_direction = 0x0p+0; + } else if (strcmp(knob_direction, "down") == 0) { + placeholder1_direction = 0x0p+0; + placeholder2_direction = 0x1p+0; + placeholder3_direction = 0x0p+0; + } else if (strcmp(knob_direction, "left") == 0) { + placeholder1_direction = -0x1p+0; + placeholder2_direction = 0x0p+0; + placeholder3_direction = 0x1p+0; + } else if (strcmp(knob_direction, "right") == 0) { + placeholder1_direction = 0x1p+0; + placeholder2_direction = 0x0p+0; + placeholder3_direction = 0x1p+0; + } else { + log_error("Invalid choice \"%s\" for option \"direction\". Line %d.", + knob_direction, + config_setting_source_line( + config_setting_get_member(setting, "direction"))); + log_error(" Valid ones are: \"up\", \"down\", \"left\", \"right\""); + script_free(output->script); + output->script = NULL; + return false; + } + struct script_specialization_context spec[] = { + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 0, .value = knob_duration}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 4, .value = placeholder1_direction}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 8, .value = placeholder2_direction}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 12, .value = placeholder3_direction}, + }; + script_specialize(output->script, spec, ARR_SIZE(spec)); + return true; +} +static struct script *script_template__fly_out(int *output_slots) { + static const struct instruction instrs[] = { + {.type = INST_BRANCH_ONCE, .rel = 30}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_LOAD, .slot = 10}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 8}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = -0x1.d70a3d70a3d75p-1, + .bx = 0x1.c51eb851eb854p+0, + .cx = 0x1.3333333333334p-3, + .ay = 0x1.2666666666666p+0, + .by = -0x1.3333333333334p-3, + .cy = 0x0p+0}}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 10}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 0}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_LOAD_CTX, .ctx = 1073741836}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 1}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD_CTX, .ctx = 1073741836}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 2}, + {.type = INST_LOAD, .slot = 1}, + {.type = INST_STORE, .slot = 3}, + {.type = INST_LOAD, .slot = 2}, + {.type = INST_STORE, .slot = 4}, + {.type = INST_BRANCH_ONCE, .rel = 25}, + {.type = INST_HALT}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_STORE_OVER_NAN, .slot = 10}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 12}, + {.type = INST_LOAD_CTX, .ctx = 24}, + {.type = INST_LOAD_CTX, .ctx = 8}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD_CTX, .ctx = 1073741832}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD_CTX, .ctx = 16}, + {.type = INST_LOAD_CTX, .ctx = 0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD_CTX, .ctx = 1073741828}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 11}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_STORE, .slot = 5}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_STORE, .slot = 6}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_STORE, .slot = 7}, + {.type = INST_BRANCH, .rel = -51}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_STORE, .slot = 9}, + {.type = INST_LOAD, .slot = 12}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 9}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 9}, + {.type = INST_HALT}, + }; + struct script *ret = malloc(offsetof(struct script, instrs) + sizeof(instrs)); + ret->len = ARR_SIZE(instrs); + ret->elapsed_slot = 8; + ret->n_slots = 13; + ret->stack_size = 3; + ret->vars = NULL; + ret->overrides = NULL; + memcpy(ret->instrs, instrs, sizeof(instrs)); + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("v-timing"), .slot = 0, .index = 0}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-x"), .slot = 1, .index = 1}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-y"), .slot = 2, .index = 2}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-x"), .slot = 3, .index = 3}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-y"), .slot = 4, .index = 4}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("opacity"), .slot = 5, .index = 5}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-opacity"), .slot = 6, .index = 6}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("blur-opacity"), .slot = 7, .index = 7}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("v-timing"), .slot = 10}; + HASH_ADD_STR(ret->overrides, name, override); + } + output_slots[0] = 1; + output_slots[1] = 2; + output_slots[2] = 3; + output_slots[3] = 4; + output_slots[4] = 5; + output_slots[5] = 7; + output_slots[6] = 6; + output_slots[7] = -1; + output_slots[8] = -1; + output_slots[9] = -1; + output_slots[10] = -1; + output_slots[11] = -1; + output_slots[12] = -1; + output_slots[13] = -1; + output_slots[14] = -1; + return ret; +} + +static bool win_script_preset__fly_out(struct win_script *output, config_setting_t *setting) { + output->script = script_template__fly_out(output->output_indices); + double knob_duration = 0x1.999999999999ap-3; + config_setting_lookup_float(setting, "duration", &knob_duration); + const char *knob_direction = "up"; + config_setting_lookup_string(setting, "direction", &knob_direction); + double placeholder1_direction; + double placeholder2_direction; + double placeholder3_direction; + if (strcmp(knob_direction, "up") == 0) { + placeholder1_direction = 0x0p+0; + placeholder2_direction = -0x1p+0; + placeholder3_direction = 0x0p+0; + } else if (strcmp(knob_direction, "down") == 0) { + placeholder1_direction = 0x0p+0; + placeholder2_direction = 0x1p+0; + placeholder3_direction = 0x0p+0; + } else if (strcmp(knob_direction, "left") == 0) { + placeholder1_direction = -0x1p+0; + placeholder2_direction = 0x0p+0; + placeholder3_direction = 0x1p+0; + } else if (strcmp(knob_direction, "right") == 0) { + placeholder1_direction = 0x1p+0; + placeholder2_direction = 0x0p+0; + placeholder3_direction = 0x1p+0; + } else { + log_error("Invalid choice \"%s\" for option \"direction\". Line %d.", + knob_direction, + config_setting_source_line( + config_setting_get_member(setting, "direction"))); + log_error(" Valid ones are: \"up\", \"down\", \"left\", \"right\""); + script_free(output->script); + output->script = NULL; + return false; + } + struct script_specialization_context spec[] = { + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 0, .value = knob_duration}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 4, .value = placeholder1_direction}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 8, .value = placeholder2_direction}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 12, .value = placeholder3_direction}, + }; + script_specialize(output->script, spec, ARR_SIZE(spec)); + return true; +} +static struct script *script_template__fly_in(int *output_slots) { + static const struct instruction instrs[] = { + {.type = INST_BRANCH_ONCE, .rel = 30}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_LOAD, .slot = 10}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 8}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_OP, .op = OP_DIV}, + { + .type = INST_CURVE, + .curve = {.type = CURVE_CUBIC_BEZIER, + .bezier = {.ax = -0x1.0f5c28f5c28f8p-1, + .bx = 0x1.051eb851eb853p+0, + .cx = 0x1.051eb851eb852p-1, + .ay = -0x1.47ae147ae146p-4, + .by = -0x1.dc28f5c28f5ccp-1, + .cy = 0x1.0147ae147ae16p+1}}, + }, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_LOAD, .slot = 10}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_STORE, .slot = 0}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_LOAD_CTX, .ctx = 1073741836}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 1}, + {.type = INST_LOAD, .slot = 0}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_LOAD_CTX, .ctx = 1073741836}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_OP, .op = OP_MUL}, + {.type = INST_STORE, .slot = 2}, + {.type = INST_LOAD, .slot = 1}, + {.type = INST_STORE, .slot = 3}, + {.type = INST_LOAD, .slot = 2}, + {.type = INST_STORE, .slot = 4}, + {.type = INST_BRANCH_ONCE, .rel = 16}, + {.type = INST_HALT}, + {.type = INST_LOAD_CTX, .ctx = 24}, + {.type = INST_OP, .op = OP_NEG}, + {.type = INST_LOAD_CTX, .ctx = 8}, + {.type = INST_OP, .op = OP_SUB}, + {.type = INST_STORE_OVER_NAN, .slot = 10}, + {.type = INST_LOAD_CTX, .ctx = 1073741824}, + {.type = INST_STORE, .slot = 11}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_STORE, .slot = 5}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_STORE, .slot = 6}, + {.type = INST_IMM, .imm = 0x1p+0}, + {.type = INST_STORE, .slot = 7}, + {.type = INST_BRANCH, .rel = -42}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_STORE, .slot = 9}, + {.type = INST_LOAD, .slot = 11}, + {.type = INST_IMM, .imm = 0x0p+0}, + {.type = INST_OP, .op = OP_ADD}, + {.type = INST_LOAD, .slot = 9}, + {.type = INST_OP, .op = OP_MAX}, + {.type = INST_STORE, .slot = 9}, + {.type = INST_HALT}, + }; + struct script *ret = malloc(offsetof(struct script, instrs) + sizeof(instrs)); + ret->len = ARR_SIZE(instrs); + ret->elapsed_slot = 8; + ret->n_slots = 12; + ret->stack_size = 3; + ret->vars = NULL; + ret->overrides = NULL; + memcpy(ret->instrs, instrs, sizeof(instrs)); + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("v-timing"), .slot = 0, .index = 0}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-x"), .slot = 1, .index = 1}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("offset-y"), .slot = 2, .index = 2}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-x"), .slot = 3, .index = 3}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-offset-y"), .slot = 4, .index = 4}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("opacity"), .slot = 5, .index = 5}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("shadow-opacity"), .slot = 6, .index = 6}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct variable_allocation *var = malloc(sizeof(*var)); + *var = (struct variable_allocation){ + .name = strdup("blur-opacity"), .slot = 7, .index = 7}; + HASH_ADD_STR(ret->vars, name, var); + } + { + struct overridable_slot *override = malloc(sizeof(*override)); + *override = (struct overridable_slot){.name = strdup("v-timing"), .slot = 10}; + HASH_ADD_STR(ret->overrides, name, override); + } + output_slots[0] = 1; + output_slots[1] = 2; + output_slots[2] = 3; + output_slots[3] = 4; + output_slots[4] = 5; + output_slots[5] = 7; + output_slots[6] = 6; + output_slots[7] = -1; + output_slots[8] = -1; + output_slots[9] = -1; + output_slots[10] = -1; + output_slots[11] = -1; + output_slots[12] = -1; + output_slots[13] = -1; + output_slots[14] = -1; + return ret; +} + +static bool win_script_preset__fly_in(struct win_script *output, config_setting_t *setting) { + output->script = script_template__fly_in(output->output_indices); + double knob_duration = 0x1.999999999999ap-3; + config_setting_lookup_float(setting, "duration", &knob_duration); + const char *knob_direction = "up"; + config_setting_lookup_string(setting, "direction", &knob_direction); + double placeholder1_direction; + double placeholder2_direction; + double placeholder3_direction; + if (strcmp(knob_direction, "up") == 0) { + placeholder1_direction = 0x0p+0; + placeholder2_direction = -0x1p+0; + placeholder3_direction = 0x0p+0; + } else if (strcmp(knob_direction, "down") == 0) { + placeholder1_direction = 0x0p+0; + placeholder2_direction = 0x1p+0; + placeholder3_direction = 0x0p+0; + } else if (strcmp(knob_direction, "left") == 0) { + placeholder1_direction = -0x1p+0; + placeholder2_direction = 0x0p+0; + placeholder3_direction = 0x1p+0; + } else if (strcmp(knob_direction, "right") == 0) { + placeholder1_direction = 0x1p+0; + placeholder2_direction = 0x0p+0; + placeholder3_direction = 0x1p+0; + } else { + log_error("Invalid choice \"%s\" for option \"direction\". Line %d.", + knob_direction, + config_setting_source_line( + config_setting_get_member(setting, "direction"))); + log_error(" Valid ones are: \"up\", \"down\", \"left\", \"right\""); + script_free(output->script); + output->script = NULL; + return false; + } + struct script_specialization_context spec[] = { + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 0, .value = knob_duration}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 4, .value = placeholder1_direction}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 8, .value = placeholder2_direction}, + {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + 12, .value = placeholder3_direction}, + }; + script_specialize(output->script, spec, ARR_SIZE(spec)); + return true; +} +struct { + const char *name; + bool (*func)(struct win_script *output, config_setting_t *setting); +} win_script_presets[] = { + {"disappear", win_script_preset__disappear}, + {"appear", win_script_preset__appear}, + {"slide-out", win_script_preset__slide_out}, + {"slide-in", win_script_preset__slide_in}, + {"fly-out", win_script_preset__fly_out}, + {"fly-in", win_script_preset__fly_in}, + {NULL, NULL}, +}; diff --git a/src/transition/meson.build b/src/transition/meson.build index 17a618d688..c7a1381943 100644 --- a/src/transition/meson.build +++ b/src/transition/meson.build @@ -1 +1 @@ -srcs += [ files('curve.c', 'script.c') ] +srcs += [files('generated/script_templates.c', 'curve.c', 'preset.c', 'script.c')] diff --git a/src/transition/preset.c b/src/transition/preset.c new file mode 100644 index 0000000000..8b5caddefa --- /dev/null +++ b/src/transition/preset.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#include +#include + +#include "config.h" +#include "preset.h" +#include "script.h" + +extern struct { + const char *name; + bool (*func)(struct win_script *output, config_setting_t *setting); +} win_script_presets[]; + +bool win_script_parse_preset(struct win_script *output, config_setting_t *setting) { + const char *preset = NULL; + if (!config_setting_lookup_string(setting, "preset", &preset)) { + log_error("Missing preset name in script"); + return false; + } + for (unsigned i = 0; win_script_presets[i].name; i++) { + if (strcmp(preset, win_script_presets[i].name) == 0) { + log_debug("Using animation preset: %s", preset); + return win_script_presets[i].func(output, setting); + } + } + log_error("Unknown preset: %s", preset); + return false; +} diff --git a/src/transition/preset.h b/src/transition/preset.h new file mode 100644 index 0000000000..f6d1a45bc5 --- /dev/null +++ b/src/transition/preset.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#pragma once + +#include + +typedef struct config_setting_t config_setting_t; +struct win_script; + +/// Parse a animation preset definition into a win_script. +bool win_script_parse_preset(struct win_script *output, config_setting_t *setting); diff --git a/src/transition/script.c b/src/transition/script.c index e5b7cf5df1..3cf107ea78 100644 --- a/src/transition/script.c +++ b/src/transition/script.c @@ -7,69 +7,18 @@ #include #include +#include "utils/dynarr.h" #include "utils/list.h" #include "utils/str.h" #include "utils/uthash_extra.h" #include "curve.h" #include "script.h" +#include "script_internal.h" -enum op { - OP_ADD = 0, - OP_SUB, - OP_MUL, - OP_DIV, - /// Exponent - OP_EXP, - /// Negation - OP_NEG, -}; - -enum instruction_type { - /// Push an immediate value to the top of the stack - INST_IMM = 0, - /// Pop two values from the top of the stack, apply operator, - /// and push the result to the top of the stack - INST_OP, - /// Load a memory slot and push its value to the top of the stack. - INST_LOAD, - /// Load from evaluation context and push the value to the top of the stack. - INST_LOAD_CTX, - /// Pop one value from the top of the stack, and store it into a memory slot. - INST_STORE, - /// Pop one value from the top of the stack, if the memory slot contains NaN, - /// store it into the memory slot; otherwise discard the value. - INST_STORE_OVER_NAN, - /// Evaluate a curve at the given point of elapsed time, push the result to - /// the top of the stack. - INST_CURVE, - /// Jump to the branch target only when the script is evaluated for the first - /// time. Used to perform initialization and such. - INST_BRANCH_ONCE, - /// Unconditional branch - INST_BRANCH, - INST_HALT, -}; - -struct instruction { - enum instruction_type type; - union { - double imm; - enum op op; - /// Memory slot for load and store - unsigned slot; - /// Context offset for load_ctx - ptrdiff_t ctx; - /// Relative PC change for branching - int rel; - /// The curve - struct { - const struct curve *curve; - double duration; - double delay; - }; - }; -}; +#define X(x) [x] = #x, +static const char *op_names[] = {OPERATORS}; +#undef X struct fragment { struct list_node siblings; @@ -104,50 +53,15 @@ struct compilation_stack { unsigned deps[]; }; -enum variable_type { - VAR_TYPE_TRANSITION, - VAR_TYPE_IMM, - VAR_TYPE_EXPR, -}; - -/// Store metadata about where the result of a variable is stored -struct variable_allocation { - UT_hash_handle hh; - char *name; - unsigned index; - /// The memory slot for variable named `name` - unsigned slot; -}; - -/// When interrupting an already executing script and starting a new script, -/// we might want to inherit some of the existing values of variables as starting points, -/// i.e. we want to "resume" animation for the current state. This is configurable, and -/// can be disabled by enabling the `reset` property on a transition. This struct store -/// where the `start` variables of those "resumable" transition variables, which can be -/// overridden at the start of execution for this use case. -struct overridable_slot { - UT_hash_handle hh; - char *name; - unsigned slot; -}; - -struct script { - unsigned len; - unsigned nslots; - unsigned stack_size; - double max_duration; - struct variable_allocation *vars; - struct overridable_slot *overrides; - struct instruction instrs[]; -}; - struct script_compile_context { struct script_context_info_internal *context_info; struct variable_allocation *vars; struct overridable_slot *overrides; + /// The memory slot for storing the elapsed time. + /// The next slot after this is used for storing the total duration of the script. + unsigned elapsed_slot; unsigned allocated_slots; unsigned max_stack; - double max_duration; const char *current_variable_name; int *compiled; struct list_node all_fragments; @@ -175,7 +89,7 @@ static void log_instruction_(enum log_level level, const char *func, unsigned in case INST_BRANCH_ONCE: logv("br_once %d", inst->rel); break; case INST_HALT: log_printf(tls_logger, level, func, "%u: halt", index); break; case INST_CURVE: log_printf(tls_logger, level, func, "%u: curve", index); break; - case INST_OP: logv("op %d (%c)", inst->op, operators[inst->op]); break; + case INST_OP: logv("op %s", op_names[inst->op]); break; case INST_LOAD: logv("load %u", inst->slot); break; case INST_STORE: logv("store %u", inst->slot); break; case INST_STORE_OVER_NAN: logv("store/nan %u", inst->slot); break; @@ -186,78 +100,39 @@ static void log_instruction_(enum log_level level, const char *func, unsigned in #define log_instruction(level, i, inst) \ log_instruction_(LOG_LEVEL_##level, __func__, i, &(inst)) -static double parse_time_unit(const char *str, const char **end) { - if (strncasecmp(str, "s", 1) == 0) { - *end = str + 1; - return 1; - } - if (strncasecmp(str, "ms", 2) == 0) { - *end = str + 2; - return 1e-3; - } - return NAN; -} - -static double parse_duration(const char *input_str, const char **end, char **err) { - const char *str = input_str; - double number = strtod_simple(str, end); - if (*end == str) { - asprintf(err, "Invalid curve definition \"%s\".", input_str); - return NAN; - } - str = *end; - double unit = parse_time_unit(str, end); - if (*end == str) { - asprintf(err, "Invalid curve definition \"%s\" (invalid time unit at \"%s\").", - input_str, str); - *end = input_str; - return NAN; - } - return number * unit; -} - -/// Parse a timing function. -/// -/// Syntax for this is the same as CSS transitions: -/// -/// Examples: -/// 1s cubic-bezier(0.1, 0.2, 0.3, 0.4) 0.4s -/// 2s steps(5, jump-end) -static const struct curve * -parse_timing_function(const char *input_str, double *duration, double *delay, char **err) { - const char *str = skip_space(input_str); - const char *end = NULL; - *duration = parse_duration(str, &end, err); - if (str == end) { - return NULL; - } - - if (*duration == 0) { - asprintf(err, "Timing function cannot have a zero duration."); - return NULL; - } - - *delay = 0; - str = skip_space(end); - if (!*str) { - return curve_new_linear(); - } - - auto curve = curve_parse(str, &end, err); - if (!curve) { - return NULL; - } - - str = skip_space(end); - if (!*str) { - return curve; - } - *delay = parse_duration(str, &end, err); - if (str == end) { - curve->free(curve); - return NULL; - } - return curve; +char *instruction_to_c(struct instruction i) { + char *buf = NULL; + switch (i.type) { + case INST_IMM: casprintf(&buf, "{.type = INST_IMM, .imm = %a},", i.imm); break; + case INST_BRANCH: + casprintf(&buf, "{.type = INST_BRANCH, .rel = %d},", i.rel); + break; + case INST_BRANCH_ONCE: + casprintf(&buf, "{.type = INST_BRANCH_ONCE, .rel = %d},", i.rel); + break; + case INST_HALT: casprintf(&buf, "{.type = INST_HALT},"); break; + case INST_CURVE:; + char *curve = curve_to_c(&i.curve); + casprintf(&buf, "{.type = INST_CURVE, .curve = %s},", curve); + free(curve); + break; + case INST_OP: + casprintf(&buf, "{.type = INST_OP, .op = %s},", op_names[i.op]); + break; + case INST_LOAD: + casprintf(&buf, "{.type = INST_LOAD, .slot = %u},", i.slot); + break; + case INST_STORE: + casprintf(&buf, "{.type = INST_STORE, .slot = %u},", i.slot); + break; + case INST_STORE_OVER_NAN: + casprintf(&buf, "{.type = INST_STORE_OVER_NAN, .slot = %u},", i.slot); + break; + case INST_LOAD_CTX: + casprintf(&buf, "{.type = INST_LOAD_CTX, .ctx = %ld},", i.ctx); + break; + } + return buf; } static char parse_op(const char *input_str, const char **end, char **err) { @@ -364,6 +239,7 @@ static inline double op_eval(double l, enum op op, double r) { case OP_MUL: return l * r; case OP_EXP: return pow(l, r); case OP_NEG: return -l; + case OP_MAX: return max2(l, r); } unreachable(); } @@ -440,7 +316,8 @@ static struct fragment *fragment_new(struct script_compile_context *ctx, unsigne return fragment; } -/// Precedence based expression parser. +/// Precedence based expression parser. Prepend fragments to `stack_entry`, or allocate a +/// new one if `stack_entry` is NULL. static bool expression_compile(struct compilation_stack **stack_entry, const char *input_str, struct script_compile_context *script_ctx, unsigned slot, bool allow_override, char **err) { @@ -452,9 +329,14 @@ static bool expression_compile(struct compilation_stack **stack_entry, const cha } // At most each character in `input_str` could map to an individual instruction auto fragment = fragment_new(script_ctx, (unsigned)len + 1); - *stack_entry = calloc(1, sizeof(struct compilation_stack) + sizeof(unsigned[len])); + if (!*stack_entry) { + *stack_entry = + calloc(1, sizeof(struct compilation_stack) + sizeof(unsigned[len])); + (*stack_entry)->exit = &fragment->next; + } else { + fragment->next = (*stack_entry)->entry_point; + } (*stack_entry)->entry_point = fragment; - (*stack_entry)->exit = &fragment->next; struct expression_parser_context ctx = { .op_stack = ccalloc(len, char), @@ -513,6 +395,7 @@ static bool expression_compile(struct compilation_stack **stack_entry, const cha free(ctx.op_stack); if (!succeeded) { free(*stack_entry); + *stack_entry = NULL; } return succeeded; } @@ -539,45 +422,48 @@ make_imm_stack_entry(struct script_compile_context *ctx, double imm, unsigned sl return entry; } +static void compilation_stack_cleanup(struct compilation_stack **stack_entry) { + free(*stack_entry); + *stack_entry = NULL; +} + static bool transition_compile(struct compilation_stack **stack_entry, config_setting_t *setting, struct script_compile_context *ctx, unsigned slot, char **out_err) { - const char *str = NULL; int boolean = 0; double number = 0; - double duration, delay; - const struct curve *curve; + struct curve curve; bool reset = false; char *err = NULL; - if (!config_setting_lookup_string(setting, "timing", &str)) { - asprintf(out_err, "Transition section does not contain a timing function. Line %d.", - config_setting_source_line(setting)); - return false; - } - curve = parse_timing_function(str, &duration, &delay, &err); - if (curve == NULL) { - asprintf(out_err, "%s Line %d.", err, config_setting_source_line(setting)); - free(err); - return false; - } - if (duration > ctx->max_duration) { - ctx->max_duration = duration; + const char *str = NULL; + if (config_setting_lookup_string(setting, "curve", &str)) { + curve = curve_parse(str, &str, &err); + if (curve.type == CURVE_INVALID) { + asprintf(out_err, "Cannot parse curve at line %d: %s", + config_setting_source_line(setting), err); + free(err); + return false; + } + } else { + curve = CURVE_LINEAR_INIT; } if (config_setting_lookup_bool(setting, "reset", &boolean)) { reset = boolean; } + BUG_ON(ctx->allocated_slots > UINT_MAX - 1); + + // The start value must take a slot, because it's overridable. auto start_slot = ctx->allocated_slots; - auto end_slot = ctx->allocated_slots + 1; - ctx->allocated_slots += 2; + ctx->allocated_slots += 1; if (!reset) { auto override = ccalloc(1, struct overridable_slot); override->name = strdup(ctx->current_variable_name); override->slot = start_slot; HASH_ADD_STR(ctx->overrides, name, override); } - struct compilation_stack *start = NULL, *end = NULL; + cleanup(compilation_stack_cleanup) struct compilation_stack *start = NULL, *end = NULL; if (config_setting_lookup_float(setting, "start", &number)) { start = make_imm_stack_entry(ctx, number, start_slot, true); } else if (!config_setting_lookup_string(setting, "start", &str)) { @@ -593,35 +479,124 @@ transition_compile(struct compilation_stack **stack_entry, config_setting_t *set return false; } + // 0 = end, 1 = duration, 2 = delay + struct instruction load_parameters[3]; if (config_setting_lookup_float(setting, "end", &number)) { - end = make_imm_stack_entry(ctx, number, end_slot, false); + load_parameters[0] = (struct instruction){ + .type = INST_IMM, + .imm = number, + }; } else if (!config_setting_lookup_string(setting, "end", &str)) { asprintf(out_err, "Transition definition does not contain a end value or " "expression. Line %d.", config_setting_source_line(setting)); return false; - } else if (!expression_compile(&end, str, ctx, end_slot, false, &err)) { - asprintf(out_err, "Transition has an invalid end expression: %s. Line %d", - err, config_setting_source_line(setting)); - free(err); + } else { + BUG_ON(ctx->allocated_slots > UINT_MAX - 1); + auto end_slot = ctx->allocated_slots++; + if (!expression_compile(&end, str, ctx, end_slot, false, &err)) { + asprintf(out_err, + "Transition has an invalid end expression: %s. Line %d", + err, config_setting_source_line(setting)); + free(err); + return false; + } + load_parameters[0] = (struct instruction){ + .type = INST_LOAD, + .slot = end_slot, + }; + } + + if (config_setting_lookup_float(setting, "duration", &number)) { + if (number == 0) { + asprintf(out_err, "Duration must be greater than 0. Line %d.", + config_setting_source_line(setting)); + return false; + } + load_parameters[1] = (struct instruction){ + .type = INST_IMM, + .imm = number, + }; + } else if (!config_setting_lookup_string(setting, "duration", &str)) { + asprintf(out_err, + "Transition definition does not contain a duration value or " + "expression. Line %d.", + config_setting_source_line(setting)); return false; + } else { + BUG_ON(ctx->allocated_slots > UINT_MAX - 1); + auto duration_slot = ctx->allocated_slots++; + if (!expression_compile(&end, str, ctx, duration_slot, false, &err)) { + asprintf(out_err, "Transition has an invalid duration expression: %s. Line %d", + err, config_setting_source_line(setting)); + free(err); + return false; + } + load_parameters[1] = (struct instruction){ + .type = INST_LOAD, + .slot = duration_slot, + }; + } + + if (config_setting_lookup_float(setting, "delay", &number)) { + load_parameters[2] = (struct instruction){ + .type = INST_IMM, + .imm = number, + }; + } else if (!config_setting_lookup_string(setting, "delay", &str)) { + load_parameters[2] = (struct instruction){ + .type = INST_IMM, + .imm = 0, + }; + } else { + BUG_ON(ctx->allocated_slots > UINT_MAX - 1); + auto delay_slot = ctx->allocated_slots++; + if (!expression_compile(&end, str, ctx, delay_slot, false, &err)) { + asprintf(out_err, "Transition has an invalid delay expression: %s. Line %d", + err, config_setting_source_line(setting)); + free(err); + return false; + } + load_parameters[2] = (struct instruction){ + .type = INST_LOAD, + .slot = delay_slot, + }; } + // clang-format off struct instruction instrs[] = { - {.type = INST_LOAD, .slot = end_slot}, + load_parameters[0], {.type = INST_LOAD, .slot = start_slot}, - {.type = INST_OP, .op = OP_SUB}, // v1 = end - start - {.type = INST_CURVE, .curve = curve, .duration = duration, .delay = delay}, - {.type = INST_OP, .op = OP_MUL}, // v2 = v1 * curve + {.type = INST_OP, .op = OP_SUB}, // v0 = end - start + {.type = INST_LOAD, .slot = ctx->elapsed_slot}, + load_parameters[2], + {.type = INST_OP, .op = OP_SUB}, // v1 = elapsed - delay + load_parameters[1], + {.type = INST_OP, .op = OP_DIV}, // v2 = v1 / duration + {.type = INST_CURVE, .curve = curve}, // v3 = curve(v2) + {.type = INST_OP, .op = OP_MUL}, // v4 = v0 * v3 {.type = INST_LOAD, .slot = start_slot}, - {.type = INST_OP, .op = OP_ADD}, // v3 = v2 + start - {.type = INST_STORE, .slot = slot}, + {.type = INST_OP, .op = OP_ADD}, // v5 = v4 + start + {.type = INST_STORE, .slot = slot}, // memory[slot] = v5 + }; + + // Instructs for calculating the total duration of the transition + struct instruction total_duration_instrs[] = { + load_parameters[1], + load_parameters[2], + {.type = INST_OP, .op = OP_ADD}, // v0 = duration + delay + {.type = INST_LOAD, .slot = ctx->elapsed_slot + 1}, + {.type = INST_OP, .op = OP_MAX}, // v1 = max(v0, total_duration) + {.type = INST_STORE, .slot = ctx->elapsed_slot + 1}, }; - if (ctx->max_stack < 2) { - // The list of instructions above needs 2 stack slots - ctx->max_stack = 2; + // clang-format on + + if (ctx->max_stack < 3) { + // The list of instructions above needs 3 stack slots + ctx->max_stack = 3; } + struct fragment *fragment = fragment_new(ctx, ARR_SIZE(instrs)); memcpy(fragment->instrs, instrs, sizeof(instrs)); fragment->ninstrs = ARR_SIZE(instrs); @@ -653,13 +628,14 @@ transition_compile(struct compilation_stack **stack_entry, config_setting_t *set ctx->once_tail = start->exit; } - if (end->ndeps > 0) { - // Otherwise, the end value is not static, luckily we can still just - // calculate it at the end of the first evaluation, since at that point - // nothing can depends on a transition's end value. However, for the - // calculation of this transition curve, we don't yet have the end value. - // So we do this: `mem[output_slot] = mem[start_slot]`, instead of compute - // it normally. + // The `end` block includes `end`, `duration`, and `delay` values. + if (end != NULL && end->ndeps > 0) { + // If we get here, the end/duration/delay values are not static, luckily + // we can still just calculate it at the end of the first evaluation, + // since at that point nothing can depends on a transition's end value. + // However, for the calculation of this transition curve, we don't yet + // have the these values, so we do this: `mem[output_slot] = + // mem[start_slot]`, instead of compute it normally. *ctx->once_end_tail = end->entry_point; ctx->once_end_tail = end->exit; @@ -681,25 +657,31 @@ transition_compile(struct compilation_stack **stack_entry, config_setting_t *set fragment->next = phi; (*stack_entry)->exit = &phi->next; } else { - // The end value has no dependencies, so it only needs to be evaluated - // once at the start of the first evaluation. And therefore we can - // evaluate the curve like normal even for the first evaluation. - *ctx->once_tail = end->entry_point; - ctx->once_tail = end->exit; + if (end != NULL) { + // The end value has no dependencies, so it only needs to be + // evaluated once at the start of the first evaluation. And + // therefore we can evaluate the curve like normal even for the + // first evaluation. + *ctx->once_tail = end->entry_point; + ctx->once_tail = end->exit; + } *next = fragment; (*stack_entry)->exit = &fragment->next; } - free(end); - free(start); + // This must happen _after_ the `end` block. + struct fragment *total_duration_fragment = fragment_new(ctx, ARR_SIZE(instrs)); + memcpy(total_duration_fragment->instrs, total_duration_instrs, + sizeof(total_duration_instrs)); + total_duration_fragment->ninstrs = ARR_SIZE(total_duration_instrs); + *ctx->once_end_tail = total_duration_fragment; + ctx->once_end_tail = &total_duration_fragment->next; + return true; } -static void instruction_deinit(struct instruction *instr) { - if (instr->type == INST_CURVE) { - instr->curve->free(instr->curve); - } +static void instruction_deinit(struct instruction * /*instr*/) { } static void fragment_free(struct fragment *frag) { @@ -945,20 +927,40 @@ script_compile_context_init(struct script_compile_context *ctx, config_setting_t HASH_ADD_STR(ctx->vars, name, alloc); } - ctx->allocated_slots = n; + ctx->allocated_slots = n + 2; + ctx->elapsed_slot = n; auto head = fragment_new(ctx, 0); ctx->head = head; ctx->once_tail = &head->once_next; ctx->tail = &head->next; - ctx->once_end_head = NULL; - ctx->once_end_tail = &ctx->once_end_head; + ctx->once_end_head = fragment_new(ctx, 2); + ctx->once_end_head->instrs[0] = (struct instruction){ + .type = INST_IMM, + .imm = 0, + }; + ctx->once_end_head->instrs[1] = (struct instruction){ + .type = INST_STORE, + .slot = ctx->elapsed_slot + 1, + }; + ctx->once_end_head->ninstrs = 2; + ctx->once_end_tail = &ctx->once_end_head->next; + ctx->max_stack = 1; +} + +unsigned script_elapsed_slot(const struct script *script) { + return script->elapsed_slot; +} + +unsigned script_total_duration_slot(const struct script *script) { + return script->elapsed_slot + 1; } struct script * script_compile(config_setting_t *setting, struct script_parse_config cfg, char **out_err) { if (!config_setting_is_group(setting)) { + casprintf(out_err, "Script setting must be a group"); return NULL; } struct script_context_info_internal *context_table = NULL; @@ -1033,13 +1035,14 @@ script_compile(config_setting_t *setting, struct script_parse_config cfg, char * auto script = script_codegen(&ctx.all_fragments, ctx.head); script->vars = ctx.vars; script->overrides = ctx.overrides; - script->max_duration = ctx.max_duration; - script->nslots = ctx.allocated_slots; + script->elapsed_slot = ctx.elapsed_slot; + script->n_slots = ctx.allocated_slots; script->stack_size = ctx.max_stack; - log_debug("Compiled script at line %d, total instructions: %d, max duration: %f, " - "slots: %d, stack size: %d\n", - config_setting_source_line(setting), script->len, script->max_duration, - script->nslots, script->stack_size); + log_debug("Compiled script at line %d, total instructions: %d, " + "slots: %d, stack size: %d, memory[%u] = total duration, memory[%u] = " + "elapsed", + config_setting_source_line(setting), script->len, script->n_slots, + script->stack_size, script->elapsed_slot + 1, script->elapsed_slot); if (log_get_level_tls() <= LOG_LEVEL_DEBUG) { log_debug("Output mapping:"); HASH_ITER2(ctx.vars, var) { @@ -1057,17 +1060,100 @@ script_compile(config_setting_t *setting, struct script_parse_config cfg, char * return script; } +char *script_to_c(const struct script *script, const struct script_output_info *outputs) { + char **buf = dynarr_new(char *, script->len * 40); + char *tmp = NULL; + casprintf(&tmp, "{\n" + " static const struct instruction instrs[] = {\n"); + dynarr_push(buf, tmp); + for (unsigned i = 0; i < script->len; i++) { + dynarr_push(buf, instruction_to_c(script->instrs[i])); + } + casprintf(&tmp, + " };\n struct script *ret = \n" + " malloc(offsetof(struct script, instrs) + sizeof(instrs));\n" + " ret->len = ARR_SIZE(instrs); ret->elapsed_slot = %u;" + " ret->n_slots = %u; ret->stack_size = %u;\n" + " ret->vars = NULL; ret->overrides = NULL;\n" + " memcpy(ret->instrs, instrs, sizeof(instrs));\n", + script->elapsed_slot, script->n_slots, script->stack_size); + dynarr_push(buf, tmp); + + struct variable_allocation *var, *next_var; + HASH_ITER(hh, script->vars, var, next_var) { + char *var_str = NULL; + casprintf(&var_str, + " {\n" + " struct variable_allocation *var = \n" + " malloc(sizeof(*var));\n" + " *var = (struct variable_allocation){\n" + " .name = strdup(\"%s\"), .slot = %u, .index = %u\n" + " };\n" + " HASH_ADD_STR(ret->vars, name, var);\n" + " }\n", + var->name, var->slot, var->index); + dynarr_push(buf, var_str); + } + + struct overridable_slot *override, *next_override; + HASH_ITER(hh, script->overrides, override, next_override) { + char *override_str = NULL; + casprintf(&override_str, + " {\n" + " struct overridable_slot *override = \n" + " malloc(sizeof(*override));\n" + " *override = (struct overridable_slot){\n" + " .name = strdup(\"%s\"), .slot = %u\n" + " };\n" + " HASH_ADD_STR(ret->overrides, name, override);\n" + " }\n", + override->name, override->slot); + dynarr_push(buf, override_str); + } + + for (size_t i = 0; outputs && outputs[i].name; i++) { + struct variable_allocation *alloc = NULL; + HASH_FIND_STR(script->vars, outputs[i].name, alloc); + if (alloc) { + casprintf(&tmp, " output_slots[%zu] = %u;\n", i, alloc->slot); + } else { + casprintf(&tmp, " output_slots[%zu] = -1;\n", i); + } + dynarr_push(buf, tmp); + } + + casprintf(&tmp, " return ret;\n}\n"); + dynarr_push(buf, tmp); + return dynarr_join(buf, ""); +} + +void script_specialize(struct script *script, + const struct script_specialization_context *spec, unsigned n_context) { + for (unsigned i = 0; i < script->len; i++) { + if (script->instrs[i].type != INST_LOAD_CTX) { + continue; + } + for (unsigned j = 0; j < n_context; j++) { + if (script->instrs[i].ctx == spec[j].offset) { + script->instrs[i].type = INST_IMM; + script->instrs[i].imm = spec[j].value; + break; + } + } + } +} + struct script_instance *script_instance_new(const struct script *script) { // allocate no space for the variable length array is UB. - unsigned memory_size = max2(1, script->nslots + script->stack_size); + unsigned memory_size = max2(1, script->n_slots + script->stack_size); struct script_instance *instance = calloc(1, sizeof(struct script_instance) + sizeof(double[memory_size])); allocchk(instance); instance->script = script; - instance->elapsed = 0; - for (unsigned i = 0; i < script->nslots; i++) { + for (unsigned i = 0; i < script->n_slots; i++) { instance->memory[i] = NAN; } + instance->memory[script->elapsed_slot] = 0; return instance; } @@ -1084,17 +1170,13 @@ void script_instance_resume_from(struct script_instance *old, struct script_inst } } -bool script_instance_is_finished(const struct script_instance *instance) { - return instance->elapsed >= instance->script->max_duration; -} - enum script_evaluation_result script_instance_evaluate(struct script_instance *instance, void *context) { auto script = instance->script; - auto stack = (double *)&instance->memory[script->nslots]; + auto stack = (double *)&instance->memory[script->n_slots]; unsigned top = 0; double l, r; - bool do_branch_once = instance->elapsed == 0; + bool do_branch_once = instance->memory[script->elapsed_slot] == 0; for (auto i = script->instrs;; i++) { switch (i->type) { case INST_IMM: stack[top++] = i->imm; break; @@ -1132,9 +1214,10 @@ script_instance_evaluate(struct script_instance *instance, void *context) { } break; case INST_CURVE: - l = (instance->elapsed - i->delay) / i->duration; + BUG_ON(top < 1); + l = stack[top - 1]; l = min2(max2(0, l), 1); - stack[top++] = i->curve->sample(i->curve, l); + stack[top - 1] = curve_sample(&i->curve, l); break; } if (top && safe_isnan(stack[top - 1])) { @@ -1171,23 +1254,28 @@ TEST_CASE(scripts_1) { c = \"(b - 1) * (a+1)\";\ d = \"- e - 1\"; \ e : { \ - timing = \"10s cubic-bezier(0.5,0.5, 0.5, 0.5) 0.5s\"; \ + curve = \"cubic-bezier(0.5,0.5, 0.5, 0.5)\"; \ + duration = \"a\"; \ + delay = 0.5; \ start = 10; \ end = \"2 * c\"; \ }; \ f : { \ - timing = \"10s cubic-bezier(0.1,0.2, 0.3, 0.4) 0.5s\"; \ + curve = \"cubic-bezier(0.1,0.2, 0.3, 0.4)\"; \ + duration = 10; \ + delay = 0.5; \ start = \"e + 1\"; \ end = \"f - 1\"; \ }; \ neg = \"-a\"; \ timing1 : { \ - timing = \"10s\"; \ + duration = 10; \ start = 1; \ end = 0; \ };\ timing2 : { \ - timing = \"10s steps(1, jump-start)\"; \ + curve = \"steps(1, jump-start)\"; \ + duration = 10; \ start = 1; \ end = 0; \ };"; @@ -1209,6 +1297,7 @@ TEST_CASE(scripts_1) { struct script_instance *instance = script_instance_new(script); auto result = script_instance_evaluate(instance, NULL); TEST_EQUAL(result, SCRIPT_EVAL_OK); + TEST_EQUAL(instance->memory[script->elapsed_slot + 1], 10.5); TEST_EQUAL(instance->memory[outputs[0].slot], 10); TEST_EQUAL(instance->memory[outputs[1].slot], 20); TEST_EQUAL(instance->memory[outputs[2].slot], 209); @@ -1216,12 +1305,12 @@ TEST_CASE(scripts_1) { TEST_EQUAL(instance->memory[outputs[4].slot], 10); TEST_TRUE(!script_instance_is_finished(instance)); - instance->elapsed += 5.5; + instance->memory[instance->script->elapsed_slot] += 5.5; result = script_instance_evaluate(instance, NULL); TEST_EQUAL(result, SCRIPT_EVAL_OK); TEST_EQUAL(instance->memory[outputs[4].slot], 214); - instance->elapsed += 5.5; + instance->memory[instance->script->elapsed_slot] += 5.5; result = script_instance_evaluate(instance, NULL); TEST_EQUAL(result, SCRIPT_EVAL_OK); TEST_EQUAL(instance->memory[outputs[0].slot], 10); @@ -1252,21 +1341,20 @@ TEST_CASE(script_errors) { static const char *cases[][2] = { {"a = \"1 @ 2 \";", "Failed to parse expression at line 1. Expected one of " "\"+-*/^\", got '@'."}, - {"a = { timing = \"1 asdf\";};", "Invalid curve definition \"1 asdf\" " - "(invalid time unit at \" asdf\"). Line 1."}, - {"a = { timing = \"1s asdf\";};", "Unknown curve type \"asdf\". Line 1."}, - {"a = { timing = \"1s steps(a)\";};", "Invalid step count at \"a)\". Line " - "1."}, - {"a = { timing = \"1s steps(1)\";};", "Invalid steps argument list \"(1)\". " - "Line 1."}, + {"a = { curve = \"asdf\";};", "Cannot parse curve at line 1: Unknown curve " + "type \"asdf\"."}, + {"a = { curve = \"steps(a)\";};", "Cannot parse curve at line 1: Invalid " + "step count at \"a)\"."}, + {"a = { curve = \"steps(1)\";};", "Cannot parse curve at line 1: Invalid " + "steps argument list \"(1)\"."}, {"a = \"1 + +\";", "Failed to parse expression at line 1. Expected a number " "or a variable name, got \"+\"."}, {"a = \"1)\";", "Failed to parse expression at line 1. Unmatched ')' in " "expression \"1)\""}, - {"a = {};", "Transition section does not contain a timing function. Line 1."}, - {"a = { timing = \"0s\"; start = 0; end = 0; };", "Timing function cannot " - "have a zero duration. " - "Line 1."}, + {"a = {};", "Transition definition does not contain a start value or " + "expression. Line 1."}, + {"a = { duration = 0; start = 0; end = 0; };", "Duration must be greater " + "than 0. Line 1."}, }; char *err = NULL; struct script *script = NULL; diff --git a/src/transition/script.h b/src/transition/script.h index 82c3bed7fd..d191eb3260 100644 --- a/src/transition/script.h +++ b/src/transition/script.h @@ -14,6 +14,11 @@ struct script_context_info { ptrdiff_t offset; }; +struct script_specialization_context { + ptrdiff_t offset; + double value; +}; + struct script_output_info { const char *name; /// Slot for this variable, -1 if this variable doesn't exist. @@ -29,7 +34,6 @@ struct script_parse_config { struct script; struct script_instance { const struct script *script; - double elapsed; double memory[]; }; enum script_evaluation_result { @@ -44,12 +48,13 @@ typedef struct config_setting_t config_setting_t; static_assert(alignof(double) > alignof(unsigned), "double/unsigned has unexpected " "alignment"); +#define SCRIPT_CTX_PLACEHOLDER_BASE (0x40000000) + struct script * script_compile(config_setting_t *setting, struct script_parse_config cfg, char **out_err); void script_free(struct script *script); enum script_evaluation_result script_instance_evaluate(struct script_instance *instance, void *context); -bool script_instance_is_finished(const struct script_instance *instance); /// Resume the script instance from another script instance that's currently running. /// The script doesn't have to be the same. For resumable (explained later) transitions, /// if matching variables exist in the `old` script, their starting point will be @@ -62,3 +67,29 @@ bool script_instance_is_finished(const struct script_instance *instance); /// configuration, in which case the user defined `start` value will always be used. void script_instance_resume_from(struct script_instance *old, struct script_instance *new_); struct script_instance *script_instance_new(const struct script *script); +/// Get the total duration slot of a script. +unsigned script_total_duration_slot(const struct script *script); +unsigned script_elapsed_slot(const struct script *script); + +/// Specialize a script instance with a context. During evaluation of the resulting +/// script, what would have been read from the context will be replaced with the hardcoded +/// value in the specialization context. +void script_specialize(struct script *instance, + const struct script_specialization_context *context, + unsigned n_context); + +/// Check if a script instance has finished. The script instance must have been evaluated +/// at least once. +static inline bool script_instance_is_finished(const struct script_instance *instance) { + return instance->memory[script_elapsed_slot(instance->script)] >= + instance->memory[script_total_duration_slot(instance->script)]; +} + +/// Generate code for a C function that will return a script identical to `script` when +/// called. The generated function will take a `int *output_slots` parameter, which it +/// will fill in, based on `outputs` passed to this function. Specifically, the generated +/// function will fill in `output_slots[i]` with the slot number of the output variable +/// named `outputs[i].name`. The generated function will return a pointer to the script. +/// This function only generates the function body, you need to provide the function +/// signature and the function name yourself. +char *script_to_c(const struct script *script, const struct script_output_info *outputs); diff --git a/src/transition/script_internal.h b/src/transition/script_internal.h new file mode 100644 index 0000000000..47bf736b24 --- /dev/null +++ b/src/transition/script_internal.h @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#pragma once + +#include + +#include "curve.h" + +#define OPERATORS \ + X(OP_ADD) \ + X(OP_SUB) \ + X(OP_MUL) \ + X(OP_DIV) \ + /* Exponent */ \ + X(OP_EXP) \ + /* Negation */ \ + X(OP_NEG) \ + X(OP_MAX) + +#define X(x) x, +enum op { OPERATORS }; +#undef X + +enum instruction_type { + /// Push an immediate value to the top of the stack + INST_IMM = 0, + /// Pop two values from the top of the stack, apply operator, + /// and push the result to the top of the stack + INST_OP, + /// Load a memory slot and push its value to the top of the stack. + INST_LOAD, + /// Load from evaluation context and push the value to the top of the stack. + INST_LOAD_CTX, + /// Pop one value from the top of the stack, and store it into a memory slot. + INST_STORE, + /// Pop one value from the top of the stack, if the memory slot contains NaN, + /// store it into the memory slot; otherwise discard the value. + INST_STORE_OVER_NAN, + /// Pop a value from the top of the stack, clamp its value to [0, 1], then + /// evaluate a curve at that point, push the result to the top of the stack. + INST_CURVE, + /// Jump to the branch target only when the script is evaluated for the first + /// time. Used to perform initialization and such. + INST_BRANCH_ONCE, + /// Unconditional branch + INST_BRANCH, + INST_HALT, +}; + +/// Store metadata about where the result of a variable is stored +struct variable_allocation { + UT_hash_handle hh; + char *name; + unsigned index; + /// The memory slot for variable named `name` + unsigned slot; +}; + +struct instruction { + enum instruction_type type; + union { + double imm; + enum op op; + /// Memory slot for load and store + unsigned slot; + /// Context offset for load_ctx + ptrdiff_t ctx; + /// Relative PC change for branching + int rel; + /// The curve + struct curve curve; + }; +}; + +/// When interrupting an already executing script and starting a new script, +/// we might want to inherit some of the existing values of variables as starting points, +/// i.e. we want to "resume" animation for the current state. This is configurable, and +/// can be disabled by enabling the `reset` property on a transition. This struct store +/// where the `start` variables of those "resumable" transition variables, which can be +/// overridden at the start of execution for this use case. +struct overridable_slot { + UT_hash_handle hh; + char *name; + unsigned slot; +}; + +struct script { + unsigned len; + unsigned n_slots; + /// The memory slot for storing the elapsed time. + /// The next slot after this is used for storing the total duration of the script. + unsigned elapsed_slot; + unsigned stack_size; + struct variable_allocation *vars; + struct overridable_slot *overrides; + struct instruction instrs[]; +}; diff --git a/src/utils/dynarr.c b/src/utils/dynarr.c new file mode 100644 index 0000000000..4c25049687 --- /dev/null +++ b/src/utils/dynarr.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#include "dynarr.h" +char *dynarr_join(char **arr, const char *sep) { + size_t total_len = 0; + dynarr_foreach(arr, i) { + total_len += strlen(*i); + } + + char *ret = malloc(total_len + strlen(sep) * (dynarr_len(arr) - 1) + 1); + size_t pos = 0; + allocchk(ret); + dynarr_foreach(arr, i) { + if (i != arr) { + strcpy(ret + pos, sep); + pos += strlen(sep); + } + strcpy(ret + pos, *i); + pos += strlen(*i); + free(*i); + } + dynarr_free_pod(arr); + ret[pos] = '\0'; + return ret; +} diff --git a/src/utils/dynarr.h b/src/utils/dynarr.h index 4c9f700512..fc56b7575e 100644 --- a/src/utils/dynarr.h +++ b/src/utils/dynarr.h @@ -72,7 +72,7 @@ static inline void dynarr_remove_swap_impl(size_t size, void *arr, size_t idx) { #define dynarr_free(arr, dtor) \ do { \ dynarr_clear(arr, dtor); \ - free((struct dynarr_header *)(arr)-1); \ + free((struct dynarr_header *)(arr) - 1); \ (arr) = NULL; \ } while (0) #define dynarr_free_pod(arr) dynarr_free(arr, (void (*)(typeof(arr)))NULL) @@ -86,7 +86,7 @@ static inline void dynarr_remove_swap_impl(size_t size, void *arr, size_t idx) { #define dynarr_resize(arr, newlen, init, dtor) \ do { \ BUG_ON((arr) == NULL); \ - dynarr_reserve((arr), (newlen)-dynarr_len(arr)); \ + dynarr_reserve((arr), (newlen) - dynarr_len(arr)); \ if ((init) != NULL) { \ for (size_t i = dynarr_len(arr); i < (newlen); i++) { \ (init)((arr) + i); \ @@ -121,9 +121,9 @@ static inline void dynarr_remove_swap_impl(size_t size, void *arr, size_t idx) { #define dynarr_remove_swap(arr, idx) \ dynarr_remove_swap_impl(sizeof(typeof(*(arr))), (void *)(arr), idx) /// Return the length of the array -#define dynarr_len(arr) (((struct dynarr_header *)(arr)-1)->len) +#define dynarr_len(arr) (((struct dynarr_header *)(arr) - 1)->len) /// Return the capacity of the array -#define dynarr_cap(arr) (((struct dynarr_header *)(arr)-1)->cap) +#define dynarr_cap(arr) (((struct dynarr_header *)(arr) - 1)->cap) /// Return the last element of the array #define dynarr_last(arr) ((arr)[dynarr_len(arr) - 1]) /// Return the pointer just past the last element of the array @@ -183,3 +183,7 @@ static inline void dynarr_remove_swap_impl(size_t size, void *arr, size_t idx) { } \ dynarr_find_ret; \ }) + +/// Concatenate a dynarr of strings into a single string, separated by `sep`. The array +/// will be freed by this function. +char *dynarr_join(char **arr, const char *sep); diff --git a/src/utils/meson.build b/src/utils/meson.build index a6272f826d..c1bf78a640 100644 --- a/src/utils/meson.build +++ b/src/utils/meson.build @@ -1 +1,11 @@ -srcs += [ files('cache.c', 'file_watch.c', 'kernel.c', 'statistics.c', 'str.c', 'misc.c') ] +srcs += [ + files( + 'cache.c', + 'dynarr.c', + 'file_watch.c', + 'kernel.c', + 'misc.c', + 'statistics.c', + 'str.c', + ), +] diff --git a/src/utils/str.h b/src/utils/str.h index 9a284e43d0..546d0c9535 100644 --- a/src/utils/str.h +++ b/src/utils/str.h @@ -4,6 +4,7 @@ #pragma once #include #include +#include #include #include #include @@ -93,3 +94,15 @@ static inline bool starts_with(const char *str, const char *needle, bool ignore_ /// reallocates it if it's not big enough. int asnprintf(char **strp, size_t *capacity, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + +/// Like `asprintf`, but it aborts the program if memory allocation fails. +static inline size_t __attribute__((format(printf, 2, 3))) +casprintf(char **strp, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int ret = vasprintf(strp, fmt, ap); + va_end(ap); + + BUG_ON(ret < 0); + return (size_t)ret; +} diff --git a/src/wm/win.c b/src/wm/win.c index 555dcb00e8..f32a230152 100644 --- a/src/wm/win.c +++ b/src/wm/win.c @@ -1773,7 +1773,9 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d log_verbose("Advance animation for %#010x (%s) %f seconds", win_id(w), w->name, delta_t); if (!script_instance_is_finished(w->running_animation_instance)) { - w->running_animation_instance->elapsed += delta_t; + auto elapsed_slot = + script_elapsed_slot(w->running_animation_instance->script); + w->running_animation_instance->memory[elapsed_slot] += delta_t; auto result = script_instance_evaluate( w->running_animation_instance, &win_ctx); if (result != SCRIPT_EVAL_OK) { diff --git a/src/wm/win.h b/src/wm/win.h index a82843c4f2..fa26e35227 100644 --- a/src/wm/win.h +++ b/src/wm/win.h @@ -261,6 +261,8 @@ struct win_script_context { double monitor_x, monitor_y; double monitor_width, monitor_height; }; +static_assert(SCRIPT_CTX_PLACEHOLDER_BASE > sizeof(struct win_script_context), + "win_script_context too large"); static const struct script_context_info win_script_context_info[] = { {"window-x", offsetof(struct win_script_context, x)}, diff --git a/tests/configs/parsing_test.conf b/tests/configs/parsing_test.conf index f27cd9aa8d..be05d00b87 100644 --- a/tests/configs/parsing_test.conf +++ b/tests/configs/parsing_test.conf @@ -427,9 +427,33 @@ window-shader-fg-rule = animations = ({ triggers = ["close", "hide"]; offset-y = { - timing = "0.2s linear"; start = 0; + duration = 0.2; end = "- window-height - window-y"; }; opacity = 1; -}) +}, { + triggers = ["open"]; + preset = "slide-in"; + duration = 1; +}, { + triggers = ["open"]; + preset = "slide-out"; + duration = 1; +}, { + triggers = ["open"]; + preset = "fly-in"; + duration = 1; +}, { + triggers = ["open"]; + preset = "fly-out"; + duration = 1; +}, { + triggers = ["open"]; + preset = "appear"; + duration = 1; +}, { + triggers = ["open"]; + preset = "disappear"; + duration = 1; +}); diff --git a/tools/animgen.c b/tools/animgen.c new file mode 100644 index 0000000000..59342d0032 --- /dev/null +++ b/tools/animgen.c @@ -0,0 +1,540 @@ +#include +#include +#include "compiler.h" // IWYU pragma: keep +#include "transition/script.h" +#include "transition/script_internal.h" +#include "utils/dynarr.h" +#include "wm/win.h" + +enum knob_type { + KNOB_NUMBER, + KNOB_CHOICE, +}; + +struct knob { + UT_hash_handle hh; + const char *name; + enum knob_type type; + + union { + struct { + double default_value; + } number; + struct { + char **choices; + unsigned n_choices; + unsigned default_choice; + }; + }; + bool emitted; +}; + +struct placeholder { + struct knob *source; + double *value_for_choices; +}; + +bool config_extra_get_float(config_setting_t *setting, double *value) { + if (config_setting_type(setting) != CONFIG_TYPE_FLOAT && + config_setting_type(setting) != CONFIG_TYPE_INT && + config_setting_type(setting) != CONFIG_TYPE_INT64) { + return false; + } + *value = config_setting_get_float(setting); + return true; +} + +bool config_extra_get_int(config_setting_t *setting, int *value) { + if (config_setting_type(setting) != CONFIG_TYPE_INT && + config_setting_type(setting) != CONFIG_TYPE_INT64) { + return false; + } + *value = config_setting_get_int(setting); + return true; +} + +char *sanitized_name(const char *name) { + char *ret = strdup(name); + for (char *p = ret; *p; p++) { + if (*p == '-') { + *p = '_'; + } + } + return ret; +} + +void free_charp(void *p) { + free(*(char **)p); +} + +#define scopedp(type) cleanup(free_##type##p) type * + +#define MAX_PLACEHOLDERS 10 + +void codegen(const char *name, const char *body, const struct placeholder *placeholders) { + auto ident = sanitized_name(name); + printf("static struct script *script_template__%s(int *output_slots)\n%s\n", + ident, body); + printf("static bool win_script_preset__%s(struct win_script *output, " + "config_setting_t *setting) {\n", + ident); + printf(" output->script = script_template__%s(output->output_indices);\n", ident); + for (size_t i = 0; i < MAX_PLACEHOLDERS; i++) { + if (placeholders[i].source == NULL || placeholders[i].source->emitted) { + continue; + } + + auto knob = placeholders[i].source; + scopedp(char) knob_ident = sanitized_name(knob->name); + knob->emitted = true; + if (knob->type == KNOB_NUMBER) { + printf(" double knob_%s = %a;\n", knob_ident, + knob->number.default_value); + printf(" config_setting_lookup_float(setting, \"%s\", " + "&knob_%s);\n", + knob->name, knob_ident); + continue; + } + printf(" const char *knob_%s = \"%s\";\n", knob_ident, + knob->choices[knob->default_choice]); + printf(" config_setting_lookup_string(setting, \"%s\", &knob_%s);\n", + knob->name, knob_ident); + for (unsigned j = 0; j < MAX_PLACEHOLDERS; j++) { + if (placeholders[j].source != knob) { + continue; + } + printf(" double placeholder%u_%s;\n", j, knob_ident); + } + for (unsigned j = 0; j < knob->n_choices; j++) { + printf(" if (strcmp(knob_%s, \"%s\") == 0) {\n", knob_ident, + knob->choices[j]); + for (unsigned k = 0; k < MAX_PLACEHOLDERS; k++) { + if (placeholders[k].source != knob) { + continue; + } + printf(" placeholder%u_%s = %a;\n", k, knob_ident, + placeholders[k].value_for_choices[j]); + } + printf(" } else "); + } + printf("{\n"); + printf(" log_error(\"Invalid choice \\\"%%s\\\" for " + "option \\\"%s\\\". Line %%d.\", knob_%s, " + "config_setting_source_line(config_setting_get_member(setting, " + "\"%s\")));\n", + knob->name, knob_ident, knob->name); + printf(" log_error(\" Valid ones are: "); + for (unsigned j = 0; j < knob->n_choices; j++) { + printf("%s\\\"%s\\\"", j ? ", " : "", knob->choices[j]); + } + printf("\");\n"); + printf(" script_free(output->script);\n"); + printf(" output->script = NULL;\n"); + printf(" return false;\n"); + printf(" }\n"); + } + printf(" struct script_specialization_context spec[] = {\n"); + for (size_t i = 0; i < 10; i++) { + if (placeholders[i].source == NULL) { + continue; + } + auto knob = placeholders[i].source; + auto knob_ident = sanitized_name(knob->name); + if (knob->type == KNOB_NUMBER) { + printf(" {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + %zu, " + ".value = knob_%s},\n", + i * 4, knob_ident); + } else { + printf(" {.offset = SCRIPT_CTX_PLACEHOLDER_BASE + %zu, " + ".value = placeholder%zu_%s},\n", + i * 4, i, knob_ident); + } + free(knob_ident); + } + printf(" };\n"); + printf(" script_specialize(output->script, spec, ARR_SIZE(spec));\n"); + printf(" return true;\n"); + printf("}\n"); + free(ident); +} + +/// Syntax for defining knobs and placeholders: +/// +/// { +/// # other settings... +/// # .... +/// +/// __knobs = { +/// knob1 = 0.5; # knob1 is a number, default value 0.5 +/// +/// # knob2 is a choice, default choice is "default_choice" (index 2) +/// # ┌----- index of the default choice +/// # v +/// knob2 = (2, ["choice1", "choice2", "default_choice"]); +/// }; +/// __placeholders = ( +/// #┌----- index of the placeholder +/// #v +/// (1, "knob1"), # placeholder1 takes value from knob1 +/// +/// # placeholder2 takes value from knob2. Because knob2 is a choice, +/// # we need to provide a mapping from choice to value. +/// (2, "knob2", [1, 2, 0]); +/// ); +/// } + +static bool parse_knobs(const char *preset_name, config_setting_t *knob_settings, + config_setting_t *placeholder_settings, struct knob *knobs, + struct placeholder *placeholders) { + struct knob *knobs_by_name = NULL; + if (config_setting_length(knob_settings) > MAX_PLACEHOLDERS) { + fprintf(stderr, "Too many knobs in %s, max %d allowed\n", preset_name, + MAX_PLACEHOLDERS); + return false; + } + if (config_setting_length(placeholder_settings) > MAX_PLACEHOLDERS) { + fprintf(stderr, "Too many placeholders in %s, max %d allowed\n", + preset_name, MAX_PLACEHOLDERS); + return false; + } + unsigned n_knobs = 0; + for (unsigned i = 0; i < (unsigned)config_setting_length(knob_settings); i++) { + auto config = config_setting_get_elem(knob_settings, i); + const char *name = config_setting_name(config); + double default_value; + auto knob = &knobs[n_knobs++]; + knob->name = strdup(name); + if (config_extra_get_float(config, &default_value)) { + knob->type = KNOB_NUMBER; + knob->number.default_value = default_value; + HASH_ADD_STR(knobs_by_name, name, knob); + n_knobs++; + continue; + } + if (!config_setting_is_list(config) || config_setting_length(config) != 2) { + fprintf(stderr, + "Invalid placeholder %s in %s, line %d. It must be a " + "list of length 2.\n", + name, preset_name, config_setting_source_line(config)); + continue; + } + + int default_choice; + config_setting_t *choices = config_setting_get_elem(config, 1); + if (!config_extra_get_int(config_setting_get_elem(config, 0), &default_choice) || + choices == NULL || !config_setting_is_array(choices)) { + fprintf(stderr, + "Invalid placeholder %s in %s, line %d. Failed to get " + "elements.\n", + name, preset_name, config_setting_source_line(config)); + continue; + } + + auto n_choices = (unsigned)config_setting_length(choices); + if (default_choice < 0 || (unsigned)default_choice >= n_choices) { + fprintf(stderr, + "Invalid knob choice in %s, knob %s line %d. Default " + "choice out of range.\n", + preset_name, name, config_setting_source_line(config)); + continue; + } + knob->type = KNOB_CHOICE; + knob->n_choices = 0; + knob->choices = malloc(n_choices * sizeof(char *)); + knob->default_choice = (unsigned)default_choice; + + bool has_error = false; + for (unsigned j = 0; j < n_choices; j++) { + auto choice = + config_setting_get_string(config_setting_get_elem(choices, j)); + if (choice == NULL) { + fprintf(stderr, + "Invalid knob choice in %s, knob %s line %d. " + "Failed to get choice.\n", + preset_name, name, + config_setting_source_line(config)); + has_error = true; + break; + } + for (unsigned k = 0; k < j; k++) { + if (strcmp(knob->choices[k], choice) == 0) { + fprintf(stderr, + "Invalid knob choice in %s, knob %s line " + "%d. Duplicate choice %s.\n", + preset_name, name, + config_setting_source_line(config), choice); + has_error = true; + break; + } + } + if (has_error) { + break; + } + knob->choices[knob->n_choices++] = strdup(choice); + } + if (has_error) { + for (unsigned j = 0; j < knob->n_choices; j++) { + free(knob->choices[j]); + } + free(knob->choices); + free((void *)knob->name); + knob->choices = NULL; + knob->name = NULL; + continue; + } + HASH_ADD_STR(knobs_by_name, name, knob); + n_knobs++; + } + + for (unsigned i = 0; i < (unsigned)config_setting_length(placeholder_settings); i++) { + auto config = config_setting_get_elem(placeholder_settings, i); + if (!config_setting_is_list(config) || config_setting_length(config) < 2) { + fprintf(stderr, + "Invalid placeholder in preset %s, line %d. Must be a " + "non-empty list.\n", + preset_name, config_setting_source_line(config)); + continue; + } + + int index; + if (!config_extra_get_int(config_setting_get_elem(config, 0), &index)) { + fprintf(stderr, + "Invalid placeholder in preset %s, line %d. Index must " + "be an integer.\n", + preset_name, config_setting_source_line(config)); + continue; + } + + auto placeholder = &placeholders[index]; + if (placeholder->source) { + fprintf(stderr, + "Invalid placeholder in preset %s, line %d. Placeholder " + "with index %d already defined.\n", + preset_name, config_setting_source_line(config), index); + continue; + } + BUG_ON(placeholder->value_for_choices != NULL); + const char *source = + config_setting_get_string(config_setting_get_elem(config, 1)); + struct knob *knob; + HASH_FIND_STR(knobs_by_name, source, knob); + if (!knob) { + fprintf(stderr, + "Invalid placeholder%d definition in %s, line " + "%d. Source knob %s not found.\n", + index, preset_name, config_setting_source_line(config), + source); + continue; + } + + if (config_setting_length(config) == 2) { + if (knob->type != KNOB_NUMBER) { + fprintf(stderr, + "Invalid placeholder%d definition in %s, line " + "%d. Source knob %s is not a number.\n", + index, preset_name, + config_setting_source_line(config), source); + continue; + } + placeholder->source = knob; + } else if (config_setting_length(config) == 3) { + config_setting_t *value_for_choices = + config_setting_get_elem(config, 2); + if (value_for_choices == NULL || + !config_setting_is_array(value_for_choices)) { + fprintf(stderr, + "Invalid placeholder%d definition in %s, line " + "%d. Failed to get elements.\n", + index, preset_name, + config_setting_source_line(config)); + continue; + } + if (knob->type != KNOB_CHOICE) { + fprintf(stderr, + "Invalid placeholder%d definition in %s, line " + "%d. Source knob %s is not a choice.\n", + index, preset_name, + config_setting_source_line(config), source); + continue; + } + if (knob->n_choices != + (unsigned)config_setting_length(value_for_choices)) { + fprintf(stderr, + "Invalid placeholder%d definition in %s, line " + "%d. Number of choices doesn't match.\n", + index, preset_name, + config_setting_source_line(config)); + continue; + } + placeholder->value_for_choices = + malloc(sizeof(double) * knob->n_choices); + for (unsigned j = 0; j < knob->n_choices; j++) { + double value; + if (!config_extra_get_float( + config_setting_get_elem(value_for_choices, j), &value)) { + fprintf(stderr, + "Invalid placeholder%d definition in %s, " + "line %d. Failed to get value.\n", + index, preset_name, + config_setting_source_line(config)); + free(placeholder->value_for_choices); + placeholder->value_for_choices = NULL; + break; + } + placeholder->value_for_choices[j] = value; + } + if (placeholder->value_for_choices == NULL) { + continue; + } + placeholder->source = knob; + } else { + fprintf(stderr, + "Invalid placeholder%d definition in %s, line %d. " + "Excessive elements.\n", + index, preset_name, config_setting_source_line(config)); + continue; + } + } + struct knob *k, *nk; + HASH_ITER(hh, knobs_by_name, k, nk) { + HASH_DEL(knobs_by_name, k); + } + return true; +} + +int main(int argc, const char **argv) { + if (argc != 2) { + return 1; + } + + log_init_tls(); + + char **presets = dynarr_new(char *, 10); + + config_t cfg; + config_init(&cfg); + config_set_auto_convert(&cfg, 1); + + if (!config_read_file(&cfg, argv[1])) { + fprintf(stderr, "Failed to read config file %s: %s\n", argv[1], + config_error_text(&cfg)); + config_destroy(&cfg); + return 1; + } + + auto settings = config_root_setting(&cfg); + + // win_script_context_info and 10 extra placeholder contexts, for + // script_specialize() + static const ptrdiff_t base = SCRIPT_CTX_PLACEHOLDER_BASE; + struct script_context_info context_info[ARR_SIZE(win_script_context_info) + MAX_PLACEHOLDERS] = { + {"placeholder0", base}, {"placeholder1", base + 4}, + {"placeholder2", base + 8}, {"placeholder3", base + 12}, + {"placeholder4", base + 16}, {"placeholder5", base + 20}, + {"placeholder6", base + 24}, {"placeholder7", base + 28}, + {"placeholder8", base + 32}, {"placeholder9", base + 36}, + }; + memcpy(context_info + 10, win_script_context_info, sizeof(win_script_context_info)); + + struct script_output_info outputs[ARR_SIZE(win_script_outputs)]; + memcpy(outputs, win_script_outputs, sizeof(win_script_outputs)); + + struct script_parse_config parse_config = { + .context_info = context_info, + .output_info = NULL, + }; + printf("// This file is generated by tools/animgen.c from %s\n", argv[1]); + printf("// This file is included in git repository for " + "convenience only.\n"); + printf("// DO NOT EDIT THIS FILE!\n\n"); + + printf("#include \n"); + printf("#include \"../script.h\"\n"); + printf("#include \"../curve.h\"\n"); + printf("#include \"../script_internal.h\"\n"); + printf("#include \"utils/misc.h\"\n"); + printf("#include \"config.h\"\n"); + for (unsigned i = 0; i < (unsigned)config_setting_length(settings); i++) { + auto sub = config_setting_get_elem(settings, i); + auto name = config_setting_name(sub); + struct knob knobs[MAX_PLACEHOLDERS] = {}; + struct placeholder placeholders[MAX_PLACEHOLDERS] = {}; + + auto knob_settings = config_setting_get_member(sub, "*knobs"); + if (knob_settings) { + auto placeholder_settings = + config_setting_get_member(sub, "*placeholders"); + BUG_ON(!placeholder_settings); + parse_knobs(name, knob_settings, placeholder_settings, knobs, + placeholders); + config_setting_remove(sub, "*knobs"); + config_setting_remove(sub, "*placeholders"); + knob_settings = NULL; + } + + char *err = NULL; + auto script = script_compile(sub, parse_config, &err); + if (!script) { + fprintf(stderr, "Failed to compile script %s: %s\n", name, err); + free(err); + continue; + } + bool has_err = false; + for (size_t j = 0; j < script->len; j++) { + if (script->instrs[j].type != INST_LOAD_CTX) { + continue; + } + if (script->instrs[j].ctx < base) { + continue; + } + size_t index = (size_t)(script->instrs[j].ctx - base) / 4; + BUG_ON(index >= ARR_SIZE(knobs)); + if (placeholders[index].source == NULL) { + fprintf(stderr, "Placeholder %zu used, but not defined\n", + index); + has_err = true; + break; + } + } + + if (!has_err) { + char *code = script_to_c(script, outputs); + codegen(name, code, placeholders); + free(code); + + dynarr_push(presets, strdup(name)); + } + for (size_t j = 0; j < MAX_PLACEHOLDERS; j++) { + if (placeholders[j].value_for_choices) { + free(placeholders[j].value_for_choices); + } + } + for (size_t j = 0; j < MAX_PLACEHOLDERS; j++) { + if (knobs[j].type == KNOB_CHOICE) { + for (unsigned k = 0; k < knobs[j].n_choices; k++) { + free(knobs[j].choices[k]); + } + free(knobs[j].choices); + } + free((void *)knobs[j].name); + } + script_free(script); + } + + config_destroy(&cfg); + + printf("struct {\n" + " const char *name;\n" + " bool (*func)(struct win_script *output, " + "config_setting_t *setting);\n" + "} win_script_presets[] = {\n"); + dynarr_foreach(presets, p) { + auto ident = sanitized_name(*p); + printf(" {\"%s\", win_script_preset__%s},\n", *p, ident); + free(*p); + free(ident); + } + printf(" {NULL, NULL},\n};\n"); + dynarr_free_pod(presets); + return 0; +} diff --git a/tools/meson.build b/tools/meson.build new file mode 100644 index 0000000000..1595bcdc8d --- /dev/null +++ b/tools/meson.build @@ -0,0 +1,8 @@ +executable( + 'animgen', + 'animgen.c', + dependencies: [ base_deps, libconfig_dep, test_h_dep, cc.find_library('m')], + link_with: [libtools], + build_by_default: false, + include_directories: picom_inc, +)