Commit 274ae457 authored by Leon Arnott's avatar Leon Arnott
Browse files

Fixed several post-release bugs.

 * Fixed a bug where, when a transition finished, if the browser experienced lag due to a large number of other elements being present, the transitioning element's visibility could become hidden briefly before Harlowe removed the transition CSS from it.
 * Fixed a bug where (set:), (put:), (move:) and (unpack:) would produce objects with invalid TwineScript_Print() methods outside of Debug Mode, thus polluting the browser console with errors.
 * Fixed a bug where Debug Mode's Enchantments panels' headers were being drawn incorrectly.
 * Greatly improved the performance of Debug Mode's panels, reducing lag caused by a large number of entries being added to a panel at once.

Closes #201.
parent 0105ea458331
......@@ -2,7 +2,16 @@
Documentation is at http://twine2.neocities.org/. See below for compilation instructions.
### 3.2.0 changes (unreleased):
### 3.2.1 changes (unreleased):
#### Bugfixes
* Fixed a bug where, when a transition finished, if the browser experienced lag due to a large number of other elements being present, the transitioning element's `visibility` could become `hidden` briefly before Harlowe removed the transition CSS from it.
* Fixed a bug where Instant macros (`(set:)`, `(put:)`, `(move:)` and `(unpack:)`) would pollute the browser console with errors when Debug Mode was off.
* Fixed a bug where Debug Mode's Enchantments panels' headers were being drawn incorrectly.
* Greatly improved the performance of Debug Mode's panels, reducing lag caused by a large number of entries being added to a panel at once.
### 3.2.0 changes (Jan 4, 2021):
#### Bugfixes
......
"use strict";
define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twineerror', 'utils/operationutils', 'engine', 'passages', 'section', 'debugmode/panel'],
($, {escape,nth,storyElement}, State, VarRef, TwineError, {objectName, isObject, toSource}, Engine, Passages, Section, Panel) => (initialError, code) => {
($, {escape,nth,storyElement,debounce}, State, VarRef, TwineError, {objectName, isObject, toSource}, Engine, Passages, Section, Panel) => (initialError, code) => {
/*
Debug Mode
......@@ -255,7 +255,7 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
This event handler updates variables whenever the state is changed. Each row of data is either a global
variable, or a temp variable stored in the localTempVariables set, above.
*/
function updateVariables() {
const updateVariables = debounce(function() {
const rows = [];
const globals = State.variables;
let count = rows.length;
......@@ -312,7 +312,7 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
pane, which displays variables but nothing else.
*/
Variables.panel[(count ? 'remove' : 'add') + 'Class']('panel-variables-empty');
}
});
/*
This is defined far below.
*/
......@@ -421,12 +421,12 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
return row.data('enchantment') === enchantment;
},
columnHead() {
return `<div class="panel-head"><th>Name</th><th>Scope</th><th>Value</th></tr>`;
return `<tr class="panel-head"><th>Scope</th><th>Value</th></div>`;
},
});
function updateEnchantments(section) {
const updateEnchantments = debounce(function(section) {
Enchantments.update(section.enchantments, section.enchantments.length);
}
});
Section.on('add', updateEnchantments).on('remove', updateEnchantments);
/*
......@@ -466,7 +466,7 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
The Storylets tab is hidden if there are no Storylets.
*/
Storylets.tab.hide();
updateStorylets = () => {
updateStorylets = debounce(() => {
const activeStorylets = Passages.getStorylets(storyletSection);
const error = (TwineError.containsError(activeStorylets));
/*
......@@ -500,7 +500,7 @@ define(['jquery', 'utils', 'state', 'internaltypes/varref', 'internaltypes/twine
if (allStorylets.length) {
Storylets.tab.show();
}
};
});
/* The above function is called by VarRef.on('set') earlier. */
/*
......
......@@ -289,7 +289,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
TwineScript_ObjectName: "a (" + name + ":) operation",
TwineScript_Unstorable: true,
// Being unstorable and not used by any other data strctures, this doesn't need a ToSource() function.
TwineScript_Print: () => Engine.options.debug && debugMessage && TwineNotifier.create(debugMessage).render()[0].outerHTML,
TwineScript_Print: () => Engine.options.debug && debugMessage && TwineNotifier.create(debugMessage).render()[0].outerHTML || '',
};
},
[rest(AssignmentRequest)])
......@@ -363,7 +363,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
TwineScript_ObjectName: "a (move:) operation",
TwineScript_Unstorable: true,
// Being unstorable and not used by any other data strctures, this doesn't need a ToSource() function.
TwineScript_Print: () => Engine.options.debug && debugMessage && TwineNotifier.create(debugMessage).render()[0].outerHTML,
TwineScript_Print: () => Engine.options.debug && debugMessage && TwineNotifier.create(debugMessage).render()[0].outerHTML || '',
};
},
[rest(AssignmentRequest)]);
......@@ -2846,7 +2846,7 @@ define(['jquery', 'macros', 'utils', 'state', 'passages', 'renderer', 'engine',
TwineScript_ObjectName: "an (assert:) operation",
TwineScript_Unstorable: true,
// Being unstorable and not used by any other data strctures, this doesn't need a ToSource() function.
TwineScript_Print: () => false,
TwineScript_Print: () => '',
}) :
/*
The appendTitleText expando is used exclusively to append the visible error text with its titleText (which is the source of the <tw-expression>
......
......@@ -21,7 +21,7 @@ define(['macros','renderer', 'utils/operationutils', 'datatypes/lambda', 'intern
TwineScript_ObjectName: "a (" + macroName + ":) macro",
TwineScript_Unstorable: true,
// Being unstorable and not used by any other data strctures, this doesn't need a ToSource() function.
TwineScript_Print: String,
TwineScript_Print: () => '',
});
/*d:
......
......@@ -100,18 +100,18 @@ define(['jquery', 'markup', 'utils/polyfills'],
when a transition is complete, and also potentially accelerates the end
of the transition if it can be skipped and an input is being held.
*/
function onTransitionComplete(el, delay, transitionSkip, endFn) {
function onTransitionComplete(el, time, delay, transitionSkip, endFn) {
/*
Each frame, reduce the delay, and potentially reduce it further if this
Each frame, reduce the duration, and potentially reduce it further if this
transition can be skipped and an input is being held.
*/
let previousTimestamp = null, elapsedRealTime = 0;
let previousTimestamp = null, elapsedRealTime = 0, duration = time + delay;
function animate(timestamp) {
if (el[0].compareDocumentPosition(document) & 1) {
delay = 0;
duration = 0;
}
if (previousTimestamp) {
delay -= (timestamp - previousTimestamp);
duration -= (timestamp - previousTimestamp);
elapsedRealTime += (timestamp - previousTimestamp);
}
previousTimestamp = timestamp;
......@@ -119,17 +119,24 @@ define(['jquery', 'markup', 'utils/polyfills'],
The test for whether a transition can be skipped is simply that any key is being held.
*/
if (transitionSkip > 0 && (keysHeldCount + buttonsHeldCount) > 0) {
delay -= transitionSkip;
duration -= transitionSkip;
el.css('animation-delay', ((Utils.cssTimeUnit(el.css('animation-delay')) || 0) - transitionSkip) + "ms");
}
if (delay <= 0) {
if (duration <= 0) {
endFn(elapsedRealTime);
}
else {
requestAnimationFrame(animate);
}
/*
If the remaining duration exceeds the initial delay (that is, if it's currently eating into the "time" interval rather than
the "delay" interval), remove the 'visibility:hidden' guard.
*/
if (duration <= time) {
el.css('visibility', '');
}
}
!delay ? animate() : requestAnimationFrame(animate);
!duration ? animate() : requestAnimationFrame(animate);
}
/*
......@@ -547,11 +554,7 @@ define(['jquery', 'markup', 'utils/polyfills'],
el[0].setAttribute('style', el[0].getAttribute('style') + "display:block !important;width:100%");
}
});
/*
Each frame, reduce the delay, and potentially reduce it further if this
transition can be skipped and an input is being held.
*/
onTransitionComplete(el, transitionTime + transitionDelay - expedite, transitionSkip, () => {
onTransitionComplete(el, transitionTime, transitionDelay - expedite, transitionSkip, () => {
/*
As a transition-out, all that needs to be done at the end is remove the element.
*/
......@@ -590,10 +593,15 @@ define(['jquery', 'markup', 'utils/polyfills'],
if (transitionOrigin) {
el.css('transform-origin', transitionOrigin);
}
el.attr("data-t8n", transIndex).addClass("transition-in").css({
el.attr("data-t8n", transIndex).addClass("transition-in").css(Object.assign({
'animation-duration': transitionTime + "ms",
'animation-delay': (transitionDelay - expedite) + "ms",
});
},
/*
For delayed animations, initially hide the transitioning element using visiblity:hidden.
onTransitionComplete will then remove this at the correct time.
*/
(transitionDelay - expedite) ? { visibility: 'hidden' } : {}));
/*
To give newly-created sections time to render and apply changers, such as (box:) to hooks,
......@@ -617,7 +625,7 @@ define(['jquery', 'markup', 'utils/polyfills'],
}
});
onTransitionComplete(el, transitionTime + transitionDelay - expedite, transitionSkip, (elapsedRealTime) => {
onTransitionComplete(el, transitionTime, transitionDelay - expedite, transitionSkip, (elapsedRealTime) => {
/*
Unwrap the wrapping... unless it contains a non-unwrappable element,
in which case the wrapping must just have its attributes removed.
......@@ -646,6 +654,30 @@ define(['jquery', 'markup', 'utils/polyfills'],
});
},
/*
A simple debouncing function that causes a function's call to only run 300ms after the last time it was called.
*/
debounce(fn) {
let animFrameID, args, lastInvokeTime = 0;
const maybeRun = () => {
if (Date.now() - lastInvokeTime > 300) {
animFrameID = 0;
fn(...args);
}
else {
animFrameID = requestAnimationFrame(maybeRun);
}
};
return function() {
lastInvokeTime = Date.now();
args = arguments;
if (animFrameID) {
cancelAnimationFrame(animFrameID);
}
animFrameID = requestAnimationFrame(maybeRun);
};
},
/*
Logging utilities
*/
......
......@@ -3,11 +3,6 @@
@mixin keyframes($animation-name) {
@keyframes #{$animation-name} {
@content;
/*
This is used for transition-ins which are delayed - .transition-in has visibility:hidden, meaning
until the animation begins, it is invisible.
*/
0%,to { visibility:visible; }
}
}
......
......@@ -9,7 +9,6 @@ $time: 0.8s;
/* Invalid/instant transition */
.transition-in {
@include animation(appear 0ms step-start);
visibility:hidden;
}
.transition-out {
@include animation(appear 0ms step-end);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment