From 6089c4de2921df0f76f605f1830fb2c04548f73c Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Mon, 14 Oct 2019 10:42:14 +0100 Subject: [PATCH] Extend refresh throttling to tiddlers having a "throttle.refresh" field See discussion here: https://groups.google.com/d/msgid/tiddlywiki/7738644f-b53f-4fb0-b0df-16243fe51795%40googlegroups.com --- core/language/en-GB/Fields.multids | 1 + core/modules/startup/render.js | 14 ++-- .../tiddlers/concepts/TiddlerFields.tid | 67 ++++++++++--------- .../Hidden Setting_ Typin Refresh Delay.tid | 6 +- .../tiddlers/mechanisms/RefreshMechanism.tid | 13 ++++ .../tiddlers/mechanisms/RefreshThrottling.tid | 16 +++++ .../mechanisms/WikificationMechanism.tid | 28 ++++++++ .../{ => plugins}/Plugin Dependencies.tid | 0 .../tiddlers/{ => plugins}/Plugin Types.tid | 0 .../tiddlers/workingwithtw/Performance.tid | 8 ++- 10 files changed, 110 insertions(+), 43 deletions(-) create mode 100644 editions/tw5.com/tiddlers/mechanisms/RefreshMechanism.tid create mode 100644 editions/tw5.com/tiddlers/mechanisms/RefreshThrottling.tid create mode 100644 editions/tw5.com/tiddlers/mechanisms/WikificationMechanism.tid rename editions/tw5.com/tiddlers/{ => plugins}/Plugin Dependencies.tid (100%) rename editions/tw5.com/tiddlers/{ => plugins}/Plugin Types.tid (100%) diff --git a/core/language/en-GB/Fields.multids b/core/language/en-GB/Fields.multids index 12b46293a..f39ea69d7 100644 --- a/core/language/en-GB/Fields.multids +++ b/core/language/en-GB/Fields.multids @@ -31,6 +31,7 @@ source: The source URL associated with a tiddler subtitle: The subtitle text for a wizard tags: A list of tags associated with a tiddler text: The body text of a tiddler +throttle.refresh: If present, throttles refreshes of this tiddler title: The unique name of a tiddler toc-link: Suppresses the tiddler's link in a Table of Contents tree if set to: ''no'' type: The content type of a tiddler diff --git a/core/modules/startup/render.js b/core/modules/startup/render.js index fe99ed21a..05ba9844d 100644 --- a/core/modules/startup/render.js +++ b/core/modules/startup/render.js @@ -25,7 +25,7 @@ var PAGE_TEMPLATE_TITLE = "$:/core/ui/PageTemplate"; // Time (in ms) that we defer refreshing changes to draft tiddlers var DRAFT_TIDDLER_TIMEOUT_TITLE = "$:/config/Drafts/TypingTimeout"; -var DRAFT_TIDDLER_TIMEOUT = 400; +var THROTTLE_REFRESH_TIMEOUT = 400; exports.startup = function() { // Set up the title @@ -78,12 +78,12 @@ exports.startup = function() { } // Add the change event handler $tw.wiki.addEventListener("change",$tw.perf.report("mainRefresh",function(changes) { - // Check if only drafts have changed - var onlyDraftsHaveChanged = true; + // Check if only tiddlers that are throttled have changed + var onlyThrottledTiddlersHaveChanged = true; for(var title in changes) { var tiddler = $tw.wiki.getTiddler(title); - if(!tiddler || !tiddler.hasField("draft.of")) { - onlyDraftsHaveChanged = false; + if(!tiddler || !(tiddler.hasField("draft.of") || tiddler.hasField("throttle.refresh"))) { + onlyThrottledTiddlersHaveChanged = false; } } // Defer the change if only drafts have changed @@ -91,10 +91,10 @@ exports.startup = function() { clearTimeout(timerId); } timerId = null; - if(onlyDraftsHaveChanged) { + if(onlyThrottledTiddlersHaveChanged) { var timeout = parseInt($tw.wiki.getTiddlerText(DRAFT_TIDDLER_TIMEOUT_TITLE,""),10); if(isNaN(timeout)) { - timeout = DRAFT_TIDDLER_TIMEOUT; + timeout = THROTTLE_REFRESH_TIMEOUT; } timerId = setTimeout(refresh,timeout); $tw.utils.extend(deferredChanges,changes); diff --git a/editions/tw5.com/tiddlers/concepts/TiddlerFields.tid b/editions/tw5.com/tiddlers/concepts/TiddlerFields.tid index 3d2dd5a84..0a0c07c7e 100644 --- a/editions/tw5.com/tiddlers/concepts/TiddlerFields.tid +++ b/editions/tw5.com/tiddlers/concepts/TiddlerFields.tid @@ -1,5 +1,5 @@ created: 20130825213300000 -modified: 20180104000000000 +modified: 20191013093910961 tags: Concepts title: TiddlerFields type: text/vnd.tiddlywiki @@ -9,43 +9,44 @@ type: text/vnd.tiddlywiki The standard fields are: -|!Field Name |!Reference |!Description | -|`title` |TitleField |<> | -|`text` |TextField |<> | -|`modified` |ModifiedField |<> | -|`modifier` |ModifierField |<> | -|`created` |CreatedField |<> | -|`creator` |CreatorField |<> | -|`tags` |TagsField |<> | -|`type` |TypeField |<> | -|`list` |ListField |<> | -|`caption` |CaptionField |<> | +|!Field Name |!Description | +|`title` |<> | +|`text` |<> | +|`modified` |<> | +|`modifier` |<> | +|`created` |<> | +|`creator` |<> | +|`tags` |<> | +|`type` |<> | +|`list` |<> -- see ListField | +|`caption` |<> | Other fields used by the core are: -|!Field Name |!Reference |!Description | -|`class` |ClassField |<> | -|`color` |ColorField |<> | -|`description` |DescriptionField |<> | -|`draft.of` |DraftOfField |<> | -|`draft.title` |DraftTitleField |<> | -|`footer` |FooterField |<> | -|`hide-body`|HideBodyField|<>| -|`icon` |IconField |<> | -|`library` |LibraryField |<> | -|`list-after` |ListAfterField |<> | -|`list-before` |ListBeforeField |<> | -|`name` |NameField |<> | -|`plugin-priority` |PluginPriorityField |<> | -|`plugin-type` |PluginTypeField |<> | -|`source` |SourceField |<> | -|`subtitle` |SubtitleField |<> | -|`toc-link`|TocLink|<>| +|!Field Name |!Description | +|`class` |<> | +|`color` |<> | +|`description` |<> | +|`draft.of` |<> | +|`draft.title` |<> | +|`footer` |<> | +|`hide-body`|<>| +|`icon` |<> | +|`library` |<> | +|`list-after` |<> | +|`list-before` |<> | +|`name` |<> | +|`plugin-priority` |<> | +|`plugin-type` |<> | +|`source` |<> | +|`subtitle` |<> | +|`throttle.refresh` |<> | +|`toc-link`|<>| The TiddlyWebAdaptor uses a few more fields: -|!Field Name |!Reference |!Description | -|`bag` |BagField |<> | -|`revision` |RevisionField |<> | +|!Field Name |!Description | +|`bag` |<> | +|`revision` |<> | Details of the fields used in this ~TiddlyWiki are shown in the [[control panel|$:/ControlPanel]] {{$:/core/ui/Buttons/control-panel}} under the <<.controlpanel-tab Info>> tab >> <<.info-tab Advanced>> sub-tab >> Tiddler Fields diff --git a/editions/tw5.com/tiddlers/hiddensettings/Hidden Setting_ Typin Refresh Delay.tid b/editions/tw5.com/tiddlers/hiddensettings/Hidden Setting_ Typin Refresh Delay.tid index 711104529..d47faf476 100644 --- a/editions/tw5.com/tiddlers/hiddensettings/Hidden Setting_ Typin Refresh Delay.tid +++ b/editions/tw5.com/tiddlers/hiddensettings/Hidden Setting_ Typin Refresh Delay.tid @@ -1,10 +1,12 @@ created: 20150619162409306 -modified: 20150619162511957 +modified: 20191014091803518 tags: [[Hidden Settings]] title: Hidden Setting: Typing Refresh Delay type: text/vnd.tiddlywiki -TiddlyWiki defers processing changes to draft tiddlers until a timeout has elapsed. The default value of 400ms gives a good balance of responsiveness in most cases but isn't always optimal on lower powered mobile devices. +TiddlyWiki defers processing changes to draft tiddlers until a timeout has elapsed (this is called throttling). The mechanism can be extended to other tiddlers by adding a `throttle.refresh` field. See RefreshThrottling for details. + +The default value of 400ms gives a good balance of responsiveness in most cases but isn't always optimal on lower powered mobile devices. The timeout can now be changed by changing this value (in milliseconds): diff --git a/editions/tw5.com/tiddlers/mechanisms/RefreshMechanism.tid b/editions/tw5.com/tiddlers/mechanisms/RefreshMechanism.tid new file mode 100644 index 000000000..e50d333b5 --- /dev/null +++ b/editions/tw5.com/tiddlers/mechanisms/RefreshMechanism.tid @@ -0,0 +1,13 @@ +created: 20191012152414236 +modified: 20191014091753894 +tags: Mechanisms +title: RefreshMechanism +type: text/vnd.tiddlywiki + +The refresh mechanism is the part of the WikificationMechanism concerned with updating a rendering when there are changes in the tiddler store. + +The refresh mechanism is notified of changes to the tiddler store asynchronously. This is done so that multiple consecutive changes can be coalesced into a single change notification. Thus, a series of action widgets modifying several different tiddlers will only trigger a single refresh cycle. + +When changes occur, the rendering is updated by calling the "refresh" method of the root widget. The refresh method determines whether the widget needs to be updated to reflect the incoming changes, and then recursively calls into the refresh methods of each child widget + +The refresh cycle is inherently fairly slow because it involves visiting every node in the render tree. To maintain performance there is a RefreshThrottling mechanism that enables refresh processing to be deferred when rapid changes occur to the same tiddler. diff --git a/editions/tw5.com/tiddlers/mechanisms/RefreshThrottling.tid b/editions/tw5.com/tiddlers/mechanisms/RefreshThrottling.tid new file mode 100644 index 000000000..3453bf2b4 --- /dev/null +++ b/editions/tw5.com/tiddlers/mechanisms/RefreshThrottling.tid @@ -0,0 +1,16 @@ +created: 20191013095916159 +modified: 20191014093837558 +tags: RefreshMechanism +title: RefreshThrottling +type: text/vnd.tiddlywiki + +The RefreshMechanism allows the refresh cycle to be throttled (or deferred) when rapid changes occur to the same tiddler. It is used to maintain responsiveness while editing a draft tiddler, but can also be used on other tiddlers. + +The rules governing refresh throttling are: + +* When a change notification occurs, throttling will only take place if all of the modified tiddlers meet at least one of these criteria: +** Has the field `draft.of` +** Has the field `throttle.refresh` +* If the refresh cycle is to be throttled, a timer is set for the internal specified in [[$:/config/Drafts/TypingTimeout|Hidden Setting: Typing Refresh Delay]] (cancelling any preciously set timer) +** When the timer fires, the refresh cycle is triggered, passing the aggregated titles of all the deferred refresh cycles + diff --git a/editions/tw5.com/tiddlers/mechanisms/WikificationMechanism.tid b/editions/tw5.com/tiddlers/mechanisms/WikificationMechanism.tid new file mode 100644 index 000000000..1683748cf --- /dev/null +++ b/editions/tw5.com/tiddlers/mechanisms/WikificationMechanism.tid @@ -0,0 +1,28 @@ +created: 20191012080221911 +modified: 20191013094002890 +tags: Mechanisms +title: WikificationMechanism +type: text/vnd.tiddlywiki + +"Wikification" is a general term for the dynamic process of converting tiddlers containing WikiText into the HTML DOM representation needed by the browser, and updating that representation if the underlying tiddlers change. + +It is composed of several distinct steps: + +* ParserMechanism: reading the text of tiddlers and scanning for wikitext constructions, outputting a tree representation of the resulting structure. It is an expensive process so parse trees are cached, and only need to be updated if the corresponding tiddler is changed +* WidgetMechanism: starting with a specified root tiddler, recursively instantiate a widget for each parse tree node making a rendering tree. Widgets can optionally also create DOM nodes +* RefreshMechanism: handling changes to the tiddler store by selectively and efficiently updating a rendering tree + +This mechanism is used in the browser to build TiddlyWiki's main interactive page. At startup, the tiddler $:/core/ui/PageTemplate is parsed and rendered to the DOM, recursively pulling in other tiddlers to build the entire user interface. Any user interactions -- following a link, clicking a button, or typing in a text box -- trigger a change in the tiddler store which then automatically propagates through the widget tree. For example, if the user clicks a link to navigate to a new tiddler, the following steps take place: + +# Clicking the link triggers the action of the LinkWidget which by default is to add the target tiddler to the list field of the tiddler $:/StoryList +# The modification to the tiddler store asynchronously triggers the refresh cycle. The asynchronous triggering ensures that the refresh cycle is only run once even if multiple tiddlers were modified in succession +# The refresh cycle recursively visits each node of the render tree giving them the chance to update themselves in the light of the accumulated changes to the tiddler store. In this case, the ListWidget of the main story river notices that a single tiddler needs to be added to the river, and renders that newly displayed tiddler without disturbing the other tiddlers + +The performance of the entire wikification process is critical. If the refresh cycle takes more than about 400ms then the user will notice a delay between their actions and the effects. See [[Performance]] for some discussion of how to optimise performance. + +The rendering process is also aggressively reused in other parts of TiddlyWiki, both in the browser and on the server: + +* Generating TiddlyWiki's standalone HTML representation +* Creating static HTML renderings of tiddlers +* Dynamically rendering CSS stylesheet tiddlers + diff --git a/editions/tw5.com/tiddlers/Plugin Dependencies.tid b/editions/tw5.com/tiddlers/plugins/Plugin Dependencies.tid similarity index 100% rename from editions/tw5.com/tiddlers/Plugin Dependencies.tid rename to editions/tw5.com/tiddlers/plugins/Plugin Dependencies.tid diff --git a/editions/tw5.com/tiddlers/Plugin Types.tid b/editions/tw5.com/tiddlers/plugins/Plugin Types.tid similarity index 100% rename from editions/tw5.com/tiddlers/Plugin Types.tid rename to editions/tw5.com/tiddlers/plugins/Plugin Types.tid diff --git a/editions/tw5.com/tiddlers/workingwithtw/Performance.tid b/editions/tw5.com/tiddlers/workingwithtw/Performance.tid index 4a98ceea8..83ee7de91 100644 --- a/editions/tw5.com/tiddlers/workingwithtw/Performance.tid +++ b/editions/tw5.com/tiddlers/workingwithtw/Performance.tid @@ -1,15 +1,20 @@ created: 20150330155120127 -modified: 20190609154450433 +modified: 20191014091943444 tags: [[Working with TiddlyWiki]] title: Performance type: text/vnd.tiddlywiki TiddlyWiki ships with defaults that are designed to get the best out of modern devices from smartphones to desktop computers. If you need to work on older, less powerful devices, or work with large amounts of content, there are a few steps you can take to improve performance. +!! Usage + * ''Avoid the "Recent" tab''. It is computationally slow to generate and update in response to tiddler changes. * ''Use the "Vanilla" theme''. The default "Snow White" theme includes visual effects like shadows, transparency and blurring that can be slow to render on older devices * ''Avoid large tiddlers''. Large bitmaps can significantly slow TiddlyWiki's performance. For example, an image taken with a modern smartphone will often be 5MB or more. Use ExternalImages whenever possible * ''Don't have too many tiddlers open at once''. Every tiddler you have open will require processing to keep it up to date as the store changes (for example, while you type into a draft tiddler). It is particularly easy when using zoomin story view to end up with dozens of tiddlers listed in the ''Open'' tab in the sidebar. Get into the habit of periodically closing all open tiddlers with the <<.icon $:/core/images/close-all-button>> ''close all'' button + +!! WikiText + * ''Use the built-in performance instrumentation''. Studying the [[performance instrumentation|Performance Instrumentation]] results can help highlight performance problems * Take advantage of indexed filter operators. The following constructions at the start of a filter run will be optimised to run many times faster than otherwise: ** `[all[tiddlers]tag[x]...` @@ -22,4 +27,5 @@ TiddlyWiki ships with defaults that are designed to get the best out of modern d ** `[all[shadows+tiddlers]field:y[x]...` ** Note that the field indexer currently defaults to indexing field values of less than 128 characters; longer values can still be searched for, but no index will be constructed ** Also note that the “field” operator is also used when the operator name is a fieldname, so, for example, `[all[shadows+tiddlers]caption[x]...` is optimised. +* Use the [[throttling|RefreshThrottling]] feature of the RefreshMechanism judiciously