Add support for animate replace=true

This commit is contained in:
2023-10-03 18:17:24 -04:00
parent c1594a1d83
commit edea43839f
3 changed files with 187 additions and 122 deletions

View File

@@ -21,7 +21,7 @@ static const char *vtname(AnimValueType vt) {
} }
uint64_t hash_encstep(uint64_t prev, std::string_view s) { static uint64_t hash_encstep(uint64_t prev, std::string_view s) {
return util::hash_string(util::HashValue(123, prev), s).first; return util::hash_string(util::HashValue(123, prev), s).first;
} }
@@ -420,102 +420,114 @@ void AnimCoreState::decode(std::string_view s) {
} }
int AnimQueue::get_size_limit() const { int AnimQueue::get_size_limit() const {
if (encqueue_ == nullptr) return 0;
StreamBuffer sb(*encqueue_); StreamBuffer sb(*encqueue_);
return sb.read_uint8(); return sb.read_uint8();
} }
int AnimQueue::get_actual_size() const { int AnimQueue::get_actual_size() const {
if (encqueue_ == nullptr) return 0;
StreamBuffer sb(*encqueue_); StreamBuffer sb(*encqueue_);
sb.read_bytes(1); sb.read_bytes(1);
return sb.read_uint8(); return sb.read_uint8();
} }
uint64_t AnimQueue::get_final_hash() const { uint64_t AnimQueue::get_final_hash() const {
if (encqueue_ == nullptr) return 0;
StreamBuffer sb(*encqueue_); StreamBuffer sb(*encqueue_);
sb.read_bytes(2); sb.read_bytes(2);
return sb.read_uint64(); return sb.read_uint64();
} }
std::string_view AnimQueue::get_final_encstep() const { std::string_view AnimQueue::get_final_encstep() const {
if (encqueue_ == nullptr) return std::string_view();
StreamBuffer sb(*encqueue_); StreamBuffer sb(*encqueue_);
sb.read_bytes(10); sb.read_bytes(10);
return sb.read_string_view(); return sb.read_string_view();
} }
void AnimQueue::update_encqueue(int limit, bool add, std::string_view add_enc, bool keepold) { AnimQueue::QueueRange AnimQueue::get_range(int lo, int hi) {
// Make sure the size limit is reasonable. // Clamp lo and hi to the valid range (0 to actual_size).
assert((limit >= 2) && (limit <= 250)); //
int actual_size = get_actual_size();
if (lo < 0) lo = 0;
if (hi > actual_size) hi = actual_size;
// You must either add a new step or retain an old step. The queue can't be empty. // Abort early if the range is empty. This avoids several edge cases.
assert(keepold || add); //
if (lo >= hi) return QueueRange(0, std::string_view());
// Find out how many old steps we'll be retaining, ignoring the size limit. // Get the entries.
int nretain = 0; //
if (keepold) nretain = get_actual_size(); std::string_view queueview(*encqueue_);
StreamBuffer sb(queueview);
// If retaining all steps would overflow the size limit, retain fewer. sb.read_bytes(2); // Skip over the header.
int retain_limit = limit; for (int i = 0; i < lo; i++) {
if (add) retain_limit -= 1; sb.read_uint64();
if (nretain > retain_limit) nretain = retain_limit; sb.read_string_view();
// Calculate the new size of the queue.
int new_size = add ? (nretain + 1) : nretain;
// If we're retaining steps, extract them from the old queue.
std::string_view retain;
if (nretain > 0) {
std::string_view oldqueue(*encqueue_);
StreamBuffer sb(oldqueue);
sb.read_bytes(2); // Skip over the header.
int pos1 = sb.total_reads();
for (int i = 0; i < nretain; i++) {
sb.read_uint64();
sb.read_string_view();
}
int pos2 = sb.total_reads();
retain = oldqueue.substr(pos1, pos2 - pos1);
} }
int pos1 = sb.total_reads();
// If we're adding a step, calculate its hash. for (int i = lo; i < hi; i++) {
uint64_t add_hash = 0; sb.read_uint64();
if (add) { sb.read_string_view();
uint64_t prev_hash = 0;
if (nretain > 0) prev_hash = get_final_hash();
add_hash = hash_encstep(prev_hash, add_enc);
} }
int pos2 = sb.total_reads();
return QueueRange(hi-lo, queueview.substr(pos1, pos2 - pos1));
}
// Finally, encode everything into a binary blob. uint64_t AnimQueue::hash_encstep(const QueueRange &prev, std::string_view s) {
uint64_t prev_hash = 0;
if (prev.size > 0) {
StreamBuffer retsb(prev.entries);
prev_hash = retsb.read_uint64();
}
return ::hash_encstep(prev_hash, s);
}
void AnimQueue::update_encqueue(int limit, bool add, std::string_view add_enc, int keeplo, int keephi) {
// Get the retained entries.
QueueRange keeprange = get_range(keeplo, keephi);
// Encode everything into a binary blob.
StreamBuffer result; StreamBuffer result;
result.write_uint8(limit); result.write_uint8(limit);
result.write_uint8(new_size); result.write_uint8(keeprange.size + (add ? 1:0));
if (add) { if (add) {
uint64_t add_hash = hash_encstep(keeprange, add_enc);
result.write_uint64(add_hash); result.write_uint64(add_hash);
result.write_string(add_enc); result.write_string(add_enc);
} }
result.write_bytes(retain); result.write_bytes(keeprange.entries);
// Replace the shared string. // Replace the shared string.
encqueue_ = std::make_shared<std::string>(result.view()); encqueue_ = std::make_shared<std::string>(result.view());
} }
AnimQueue::AnimQueue() { AnimQueue::AnimQueue() {
update_encqueue(10, true, AnimState().encode(), false); update_encqueue(10, true, AnimState().encode(), 0, 0);
} }
void AnimQueue::clear(const AnimState &state) { void AnimQueue::clear(const AnimState &state) {
update_encqueue(get_size_limit(), true, state.encode(), false); update_encqueue(get_size_limit(), true, state.encode(), 0, 0);
} }
void AnimQueue::clear() { void AnimQueue::clear() {
update_encqueue(get_size_limit(), true, AnimState().encode(), false); update_encqueue(get_size_limit(), true, AnimState().encode(), 0, 0);
} }
void AnimQueue::set_limit(int limit) { void AnimQueue::set_limit(int limit) {
update_encqueue(limit, false, "", true); assert((limit >= 2) && (limit <= 250));
update_encqueue(limit, false, std::string_view(), 0, limit);
} }
void AnimQueue::add(const AnimState &state) { void AnimQueue::add(const AnimState &state) {
update_encqueue(get_size_limit(), true, state.encode(), true); int limit = get_size_limit();
update_encqueue(limit, true, state.encode(), 0, limit - 1);
}
void AnimQueue::replace(const AnimState &state) {
int limit = get_size_limit();
update_encqueue(limit, true, state.encode(), 1, limit);
} }
void AnimQueue::serialize(StreamBuffer *sb) const { void AnimQueue::serialize(StreamBuffer *sb) const {

View File

@@ -268,6 +268,13 @@ public:
// //
void add(const AnimState &state); void add(const AnimState &state);
// Replace the most recent animation step.
//
// Note: replace does not automatically compose the step with the previous
// step, you have to do that yourself.
//
void replace(const AnimState &state);
// Serialize or deserialize to a StreamBuffer // Serialize or deserialize to a StreamBuffer
// //
void serialize(StreamBuffer *sb) const; void serialize(StreamBuffer *sb) const;
@@ -316,13 +323,40 @@ public:
util::SharedStdString get_encoded_queue() const { return encqueue_; } util::SharedStdString get_encoded_queue() const { return encqueue_; }
private: private:
// Update the encoded queue. struct QueueRange {
int size;
std::string_view entries;
QueueRange(int sz, std::string_view ent) : size(sz), entries(ent) {}
};
// Get a range of entries from the queue.
// //
// You must specify the new size limit. // You must specify a range (lo-hi) of steps. In this numbering, 0 is the
// You may optionally specify an encstep to add. // most recent entry in the queue. The indices lo and hi are automatically
// If keepold, then old steps will be retained up to the size limit. // clamped to the valid range (0 to actual_size). If lo >= hi, then an
// empty range is returned.
// //
void update_encqueue(int limit, bool add, std::string_view add_enc, bool keepold); QueueRange get_range(int lo, int hi);
// Hash a new step given the range of steps that precede it.
//
static uint64_t hash_encstep(const QueueRange &prev, std::string_view step);
// Update the animation queue.
//
// The range (keeplo to keephi) specifies which old steps should be
// retained. The numbers keephi and keeplo are automatically clamped
// so that they lie inside the actual size of the queue.
//
// If add is true, then an additional step is added to the queue.
// The hash of the new step is calculated automatically.
//
// There is no enforcement that you respected the size limit that you
// specified. For example, you could say "keep 0-5, and add 1." That
// would make 6 entries in the queue. It is up to the caller to respect
// the size limit. The value passed in is just for reporting.
//
void update_encqueue(int limit, bool add, std::string_view add_enc, int keeplo, int keephi);
// Read values from the header of the encqueue. // Read values from the header of the encqueue.
// //

View File

@@ -45,85 +45,85 @@ LuaDefine(tangible_animdebug, "tan",
return LS.result(); return LS.result();
} }
LuaDefine(tangible_animstate, "tan", LuaDefine(tangible_animfinal, "tan",
"|Return the animation state variables of the tangible as a table." "|Return the final step in the animation queue."
"|" "|"
"|The animation system stores 'animation state variables. " "|The animation queue stores animation steps. This function returns"
"|There are several builtin animation state variables. The" "|the final animation step. An animation step consists of key-value"
"|following is a list, along with some example values that" "|pairs. Some of those key-value pairs describe the last thing that"
"|might be used if the object were a pirate treasure chest." "|happened to the tangible. Others describe the final resting place"
"|of the tangible."
"|" "|"
"|For example, if the tangible were a pirate chest, the key-value"
"|pairs might be:"
"|"
"| action='open' # last thing the chest did"
"| xyz={1,2,3} # xyz coordinate" "| xyz={1,2,3} # xyz coordinate"
"| plane='earth' # plane where the chest is located" "| plane='earth' # plane where the chest is located"
"| facing=0 # rotation of the chest" "| facing=0 # rotation of the chest"
"| bp='BP_piratechest' # name of an unreal blueprint"
"| model='SM_piratechest' # name of an unreal mesh"
"|"
"|There can also be user-defined animation state variables."
"|For example, for a pirate chest, you might want to add two"
"|more state variables:"
"|"
"| open=true # chest can be open or closed" "| open=true # chest can be open or closed"
"| fullness=0.8 # how big the heap of coins is" "| fullness=0.8 # how big the heap of coins is"
"|" "|"
"|All state variables must be one of four types: number, string," "|See doc(tangible.animinit) for more information about animation queues."
"|boolean, or coordinate. All state variables must have simple"
"|identifiers for names."
"|"
"|Animation state variables are updated when you use"
"|tangible.animate to create an animation record. For example,"
"|suppose your character is initialized at xyz={1,1,1}. Then"
"|he walks to xyz={2,2,2}, then he walks to xyz={3,3,3}."
"|The animation queue now contains three animation steps:"
"|"
"|Animation Queue:"
"| Step 1: action:initialize xyz={1,1,1} ..."
"| Step 2: action:walkto xyz={2,2,2} ..."
"| Step 3: action:walkto xyz={3,3,3} ..."
"|"
"|Notice that the state variable xyz is stored three times, once"
"|in each animation step. All animation state variables are stored"
"|in every animation step. When you use tangible.animstate to fetch"
"|the current value of the animation state variables, you're"
"|actually fetching the state variables from the last step in"
"|the animation queue."
"|"
"|You can use this function, tangible.animstate, to view the"
"|current set of state variables. You can use tangible.animinit"
"|to reconfigure the set of user-defined state variables. You"
"|can use tangible.animate to create animation steps, which can"
"|alter any of the state variables."
"|") { "|") {
LuaArg tanobj; LuaArg tanobj;
LuaRet result; LuaRet result;
LuaDefStack LS(L, tanobj, result); LuaDefStack LS(L, tanobj, result);
World *w = World::fetch_global_pointer(L); World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false); Tangible *tan = w->tangible_get(LS, tanobj, false);
AnimState state = tan->anim_queue_.get_final_persistent(); AnimState state = tan->anim_queue_.get_final_everything();
state.to_lua(LS, result, true); state.to_lua(LS, result, true);
return LS.result(); return LS.result();
} }
LuaDefine(tangible_animinit, "tan,config", LuaDefine(tangible_animinit, "tan,config",
"|Reinitialize the animation queue." "|Reinitialize the animation queue and specify persistent state."
"|" "|"
"|The animation queue stores certain animation state variables." "|Every tangible has an animation queue. The queue consists of a"
"|See doc(tangible.animstate) for more information about animation" "|sequence of animation steps. Each step consists of a list of"
"|state variables." "|key-value pairs. For example, if you want a human person to jump"
"|three feet in the air, you might find this animation step in the"
"|animation queue:"
"|"
"| action='jump' - the name of the animation to perform"
"| height=3.0 - the height to which you want him to jump"
"| xyz={1,2,3} - person's xyz coordinate during the jump"
"| plane=earth - plane where the jump takes place"
"|"
"|Some of those key-value pairs are 'persistent'. For example, xyz is"
"|persistent. That means that the player must always have an xyz"
"|coordinate. Every single animation step in the queue must"
"|contain a value for xyz. Likewise, 'plane' is a persistent variable."
"|The player must always be on some plane or another."
"|"
"|When you add an animation step to the animation queue, you do not have"
"|to always specify xyz and plane. For example, you can legally say:"
"|"
"| tangible.animate(a, nil, {action='jump', height=3.0}))"
"|"
"|This adds a step to the animation queue. That step contains"
"|xyz and plane, even though we didn't specify xyz and plane in"
"|the 'animate' command above. The values for xyz and plane will be"
"|copied over from the previous animation step. In this way, those values"
"|get persisted: they stay the same unless you change them in"
"|the 'animate' command."
"|"
"|There are five hardwired persistent variables: plane,xyz,facing,bp,model."
"|These five variables are persistent no matter what. This function,"
"|tangible.animinit, optionally allows you to create more persistent"
"|variables. For example, let's say you have a pirate chest. You might"
"|want to add two persistent variables in addition to the usual set:"
"|"
"| open=true - whether the chest is open or closed"
"| heapsize=0.8 - the size of the heap of coins"
"|"
"|Making a variable persistent means that it will always have a value"
"|no matter what you do."
"|" "|"
"|This function, tangible.animinit, is used to reconfigure the set of" "|This function, tangible.animinit, is used to reconfigure the set of"
"|user-defined state variables. The config table that you pass in must" "|persistent state variables that are retained by the tangible's"
"|be key-value pairs. Keys must be simple identifiers. Values can be" "|animation queue. You must provide a table containing all the"
"|numbers, strings, booleans, or coordinates." "|persistent values you want, and their initial values."
"|"
"|After tangible.animinit, the tangible's animation state variables will"
"|consist of the user-defined variables listed in the config table,"
"|plus all the builtin animation state variables."
"|"
"|Optionally, the config table can also supply values for some or all"
"|of the builtin state variables. For example, you could supply xyz"
"|in the config table. If you do, the tangible will move to a new xyz"
"|coordinate. If not, the tangible will retain its old xyz coordinate."
"|") { "|") {
LuaArg tanobj, config; LuaArg tanobj, config;
LuaDefStack LS(L, tanobj, config); LuaDefStack LS(L, tanobj, config);
@@ -144,46 +144,65 @@ LuaDefine(tangible_animinit, "tan,config",
return LS.result(); return LS.result();
} }
LuaDefine(tangible_animate, "tan,options,config",
LuaDefine(tangible_animate, "tan,config",
"|Add an animation step to the tangible." "|Add an animation step to the tangible."
"|" "|"
"|The animation queue stores animation steps. This function, " "|The animation queue stores animation steps. This function, "
"|tangible.animate, adds one new animation step to the queue." "|tangible.animate, adds one new animation step to the queue."
"|" "|"
"|It might be useful to read about 'animation state variables' before" "|It might be useful to read doc(tangible.animinit) before reading"
"|reading this explanation. See doc(tangible.animstate)." "|more."
"|" "|"
"|An animation step is just a list of key-value pairs. Therefore," "|An animation step is just a list of key-value pairs. Therefore,"
"|the config table must be key-value pairs. Keys must be simple" "|the config table must be key-value pairs. Keys must be simple"
"|identifiers. Values can be numbers, strings, booleans, or" "|identifiers. Values can be numbers, strings, booleans, or"
"|coordinates." "|coordinates."
"|" "|"
"|Some of the key-value pairs may match the name of an animation state" "|Some of the key-value pairs may match the name of a persistent"
"|variable. If so, that key-value pair permanently changes the" "|variable. If so, that key-value pair permanently changes the"
"|value of that animation state variable. The new value will" "|value of that persistent variable. The new value will"
"|be retained for all future animation steps." "|be retained for all future animation steps."
"|" "|"
"|Some of the key-value pairs may NOT match the name of any animation" "|Some of the key-value pairs may not match the name of any persistent"
"|state variable. If so, that key-value pair is part of the" "|variable. If so, that key-value pair is part of the"
"|animation step, but nothing is propagated forward to future animation" "|animation step, but nothing is propagated forward to future animation"
"|steps." "|steps."
"|" "|"
"|The options can be nil, or options can be a table containing"
"|the following flags:"
"|"
"| replace: if true, then the last step in the queue is removed,"
"| and the new animation replaces it. Persistent state is carried"
"| over from the step that was replaced."
"|"
"|") { "|") {
LuaArg tanobj, config; LuaArg tanobj, options, steptab;
LuaDefStack LS(L, tanobj, config); LuaVar option;
LuaDefStack LS(L, option, tanobj, options, steptab);
bool replace = false;
if (!LS.isnil(options)) {
LuaKeywordParser kp(LS, options);
if (kp.parse(option, "replace")) {
replace = LS.ckboolean(option);
}
}
World *w = World::fetch_global_pointer(L); World *w = World::fetch_global_pointer(L);
Tangible *tan = w->tangible_get(LS, tanobj, false); Tangible *tan = w->tangible_get(LS, tanobj, false);
AnimState state = tan->anim_queue_.get_final_persistent(); AnimState state = tan->anim_queue_.get_final_persistent();
eng::string error = state.apply_lua(LS, config, false); eng::string error = state.apply_lua(LS, steptab, false);
if (!error.empty()) { if (!error.empty()) {
luaL_error(L, "%s", error.c_str()); luaL_error(L, "%s", error.c_str());
} }
tan->anim_queue_.add(state); if (replace) {
tan->anim_queue_.replace(state);
} else {
tan->anim_queue_.add(state);
}
tan->update_plane_item(); tan->update_plane_item();
return LS.result(); return LS.result();
} }
LuaDefine(tangible_setclass, "tan,class", LuaDefine(tangible_setclass, "tan,class",
"|Set the class of the tangible." "|Set the class of the tangible."
"|The class can be a 'class table' (ie, a table of methods), " "|The class can be a 'class table' (ie, a table of methods), "