mirror of
https://github.com/yshui/picom.git
synced 2025-02-10 15:45:57 -05:00
Merge pull request #1305 from yshui/animation-presets
Animation part 4: Presets
This commit is contained in:
commit
1cfd2a0d98
24 changed files with 2864 additions and 471 deletions
|
@ -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
|
||||
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -51,8 +51,10 @@ target
|
|||
!/tests/testcases/*.py
|
||||
|
||||
# Misc files
|
||||
.vscode
|
||||
*.conf
|
||||
!/tests/configs/*.conf
|
||||
!/data/*.conf
|
||||
perf.data
|
||||
perf.data.old
|
||||
core.*
|
||||
|
|
165
data/animation_presets.conf
Normal file
165
data/animation_presets.conf
Normal file
|
@ -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]),
|
||||
);
|
||||
}
|
|
@ -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')
|
||||
|
|
|
@ -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.",
|
||||
|
|
122
src/meson.build
122
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('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') ]
|
||||
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',
|
||||
),
|
||||
]
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
// Cubic bezier interpolator.
|
||||
//
|
||||
// Stolen from servo:
|
||||
// https://searchfox.org/mozilla-central/rev/5da2d56d12/servo/components/style/bezier.rs
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
struct step_curve {
|
||||
struct curve base;
|
||||
int steps;
|
||||
bool jump_start, jump_end;
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,20 +2,51 @@
|
|||
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
||||
|
||||
#pragma once
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// ========================== Interpolators ==========================
|
||||
|
||||
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 {
|
||||
CURVE_LINEAR,
|
||||
CURVE_CUBIC_BEZIER,
|
||||
CURVE_STEP,
|
||||
CURVE_INVALID,
|
||||
};
|
||||
|
||||
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);
|
||||
struct curve {
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
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);
|
||||
|
|
1265
src/transition/generated/script_templates.c
Normal file
1265
src/transition/generated/script_templates.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1 +1 @@
|
|||
srcs += [ files('curve.c', 'script.c') ]
|
||||
srcs += [files('generated/script_templates.c', 'curve.c', 'preset.c', 'script.c')]
|
||||
|
|
30
src/transition/preset.c
Normal file
30
src/transition/preset.c
Normal file
|
@ -0,0 +1,30 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
||||
|
||||
#include <libconfig.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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;
|
||||
}
|
12
src/transition/preset.h
Normal file
12
src/transition/preset.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
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);
|
|
@ -7,69 +7,18 @@
|
|||
#include <libconfig.h>
|
||||
#include <uthash.h>
|
||||
|
||||
#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;
|
||||
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;
|
||||
}
|
||||
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:
|
||||
/// <duration> <timing-function> <delay>
|
||||
/// 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;
|
||||
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 @@ end:
|
|||
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);
|
||||
return false;
|
||||
} 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,
|
||||
};
|
||||
}
|
||||
|
||||
struct instruction instrs[] = {
|
||||
{.type = INST_LOAD, .slot = end_slot},
|
||||
{.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_LOAD, .slot = start_slot},
|
||||
{.type = INST_OP, .op = OP_ADD}, // v3 = v2 + start
|
||||
{.type = INST_STORE, .slot = slot},
|
||||
};
|
||||
if (ctx->max_stack < 2) {
|
||||
// The list of instructions above needs 2 stack slots
|
||||
ctx->max_stack = 2;
|
||||
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[] = {
|
||||
load_parameters[0],
|
||||
{.type = INST_LOAD, .slot = start_slot},
|
||||
{.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}, // 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},
|
||||
};
|
||||
// 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;
|
||||
|
|
|
@ -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);
|
||||
|
|
98
src/transition/script_internal.h
Normal file
98
src/transition/script_internal.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <uthash.h>
|
||||
|
||||
#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[];
|
||||
};
|
26
src/utils/dynarr.c
Normal file
26
src/utils/dynarr.c
Normal file
|
@ -0,0 +1,26 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
]
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#pragma once
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)},
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
540
tools/animgen.c
Normal file
540
tools/animgen.c
Normal file
|
@ -0,0 +1,540 @@
|
|||
#include <libconfig.h>
|
||||
#include <uthash.h>
|
||||
#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 <libconfig.h>\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;
|
||||
}
|
8
tools/meson.build
Normal file
8
tools/meson.build
Normal file
|
@ -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,
|
||||
)
|
Loading…
Add table
Reference in a new issue