Some thoughts on performance

I have added some thoughts on using $eventcatcher and the use of wikitext in stylesheets.

2 Likes

Great stuff @saqimtiaz - I think I understand the stylesheets piece, but having trouble with the eventcatcher one. How do you replace using buttons if you “think you” need buttons?

1 Like

In TiddlyWiki if you think you need $button widgets, it is because you want to be able to click on something to invoke actions.

As an example imagine a long list generated by a $list widget that creates a button for each list item:

<$list filter="....">
<$button tag="div" class="myclass" actions=<<myactions>> />
</$list>

Alternatively you can do something along these lines, which tells the $eventcatcher to listen for clicks inside DIVs with the class “myclass” and invoke actions in response.

<$eventcatcher $click=<<myactions>> selector="div.myclass">
<$list filter="....">
<div class="myclass" />
</$list>
</$eventcatcher>

Not only is having a bunch of DIVs faster than $button widgets, but this also opens the door to be able to use static pre-rendered lists in some situations.

Beautiful, that’s probably exactly what I need for the bullets plugin for selecting inactive lines!

Static/pre-rendered as in pre-declared?

<$eventcatcher $click= ...>
 <div ... />
 <div ... />
 <div ... />
  ...
 <div ... />
</$eventcatcher>

Interesting.

EDIT: That’s pure gold! THANK YOU @saqimtiaz !!!

:trophy:

Okay, so event delegation is almost trivial in JavaScript but to see it working in wikitext with a couple of widgets and a little wiring… wow. :sunglasses:

There is a way to go around that, with the subfilter operator :

<$vars list="A B C D E +[allbefore:include[C]]" > {{{ [subfilter<list>] }}}

This can also be used to use several variables in one filter :

<$vars a="1" b="[<a>add[1]]"> {{{ [subfilter<b>] }}} = 2

Is $vars more performant than the new $let ? If not, shouldn’t the var widget be entirely replaced by the new let widget ?

Seems like this might become the basis for a WYSIWYG editor view template (though it might work a little different than true WYSIWY editors).

It is worth keeping in mind that a lot of these performance implications only matter in practice when you reach sufficient complexity and scale with your code. The average user probably needs never think about this unless perhaps writing code for others, hence why this discussion is in the developer’s area.

If your list content will change rarely enough, you can use the change itself as the trigger to render and save it, and then re-use until the next time it changes. Wont be worthwhile unless you really are dealing with very large lists and then Dynaview would be an alternative way to approach it.

@telumire there are many ways to return multiple items from $vars or $let. The intent here is to provide guidance as to when it might make sense to use $set. $let is only more performant than $vars if it allows you to use fewer widgets, and is a lot more flexible in that sense. We cannot modify the behaviour of $vars due to backwards compatibility, therefore we have a new widget $let. $vars should really be considered deprecated.

@Mark_S yes as long as each “chunk” of content is wrapped in an identifying DOM node that is possible. On a macro scale that is what Streams does to know which node you want to edit.

Since the variables provided to actions by $eventcatcher also provide information on the location of the clicks, you could even try to implement something like an etch-a-sketch. This demo uses the $eventcatcher to know which list item a user has swiped on and animates it in response to the swipe:

1 Like

Note: As of 17/03/2022 this no longer an issue in TW 5.2.2 pre-release. The CSS is only updated when it has changed, regardless of underlying widgets used in stylesheets.

A quick note follows which I will try to elaborate on better when the opportunity presents itself:

A recommendation if using wikitext in stylesheets:
Avoid using filtered transclusions.

So instead of:

.myclass [data-item-title="{{{ [{active}escapecss[]] }}}"] {
	color: #fff;
}

do this instead:

<$let activeTitle={{{ [{active}escapecss[]] }}}>
.myclass [data-item-title="<$text text=<<activeTitle>>/>"] {
	color: #fff;
}
</$let>

The problem with a filtered transclusion is that you are creating a $list widget where the default item template is a link. Not only is that more overhead in terms of rendering, but links refresh more aggressively and this can lead to unexpected refreshing of the stylesheet. (If one part of the stylesheet refreshes, the entire stylesheet needs to be refreshed.)

For example in the code above with a filtered transclusion, if the tiddler whose name is saved in the tiddler “active” changes, this will force the entire stylesheet to be re-rendered which is not what one would expect. This is because under the covers a link is being generated to that tiddler whose name is stored in the tiddler “active” and this link needs to be refreshed every time that tiddler changes, in case the tiddler becomes/or stops being a missing tiddler. This in turn triggers the entire stylesheet needing to be refreshed.

However with the $let approach the stylesheet will only be refreshed when the value of the “active” tiddler changes, which is what one would expect.

Edit: the nuance here is that the entire stylesheet is replaced/updated - potentially on every keystroke in some situations - despite the CSS not having changed at all. The natural assumption is that the stylesheet is updated when the CSS has changed.

4 Likes

If the issue is that the filtered transclusion output a link, why not assign the filter directly to the text widget or use another template for the filtered transclusion? Would that work too ?

.myclass [data-item-title="<$text text={{{ [{active}escapecss[]] }}}/>"] {
	color: #fff;
}

or

.myclass [data-item-title="{{{ [{active}escapecss[]]||$:/core/templates/canonical-uri-external-raw}}}"] {
	color: #fff;
}

Alternatively, a custom template with this content : 

<$view field="title"/>

The intent of my post is to point out a problematic code pattern to avoid, rather than to recommend a specific alternative.

There are several alternatives. The one I posted is a derivative of code I was working with where I needed to reference the output of the filter more than once and therefore a $let widget is a better choice than assigning directly to a $text widget.

Filtered transclusions are a poor choice in this scenario from a performance point of view, as previously stated the widget tree created is more complex. Not to mention that having to create or reference another template that you cannot see in situ is also going to be error-prone.

2 Likes

I see, that make sense. Thanks !

As of 17/03/2022 this no longer an issue in TW 5.2.2 pre-release. The CSS is only updated when it has changed, regardless of underlying widgets used in stylesheets.

1 Like

4 posts were split to a new topic: Reveal/list widget, performance and alternatives

I have moved the discussion on potential alternatives to $reveal/$list that are not present at this moment in the TiddlyWiki core into a separate topic. That should allow proper discussion of new ideas while keeping this topic focused on actionable advice pertaining to the core.

2 Likes

@saqimtiaz: How about defining variables using the \define pragma? How does that affect refreshing?
In particular, does it make a difference if I define a (sub)filter using

\define filter-definition() [ some filter definition goes here ]

lots of code goes here

<$list filter="[subfilter<filter-definition>]">
    more code goes here
</$list>

or

lots of code goes here

<$let filter-definition="[ some filter definition goes here ]">
    <$list filter="[subfilter<filter-definition>]">
        more code goes here
    </$list>
</$let>

Since the variable is basically just a constant string that never changes, I’d assume there’d be not much of a difference.
What about the case when filter-definition contains a variable reference so that it might change?

Have a nice day
Yaisog

@Yaisog apologies, I did not see your questions until now.

Of the widgets that assign variables, the $list widget is a special case. It only entirely re-renders itself and its contents if the attributes assigned to the widget change. If only the output of the filter changes, it is heavily optimised to try and re-use and move around as many of the existing items as possible (under the covers each list item is an $listitem widget).

With regards to the \define pragma, it defines a variable and as long as you are not doing any text substitution the performance should be identical to using the $let widget.

The $list widget will handle this well and try to refresh only the list items necessary.

However, other widgets like $let, $set, $vars will destroy themselves and their children and then recreate everything when the value of the variable assigned changes. This is because re-creating itself and the child widgets is the only way to propagate the new variable value.

One of the things that I have been experimenting with and still needs more work, is a technique using IntersectionObserver that suspends refresh for widgets that are not visible. There are edge cases where this is causing issue but the performance gains are considerable with large or complex layouts.

@saqimtiaz: Thanks for your explanation.

Just to make sure I got all of this correct: In this code

lots of code goes here

<$let filter-definition="[ some filter definition goes here ]">
    <$list filter="[subfilter<filter-definition>]">
        more code goes here
    </$list>
</$let>

since the content assigned to filter-definition is just a (constant) string and not the filter result, this use of subfilter<filter-definition> will not result in the destruction of the $list (as a child of $let) when the actual result of the subfilter changes?

Also, pertaining to my initial question, you recommend not to put more content into $let than is necessary (because all of it is destroyed when the variable value changes). However, the scope of \define is the whole tiddler – so when the output of the macro changes, everything in the tiddler is destroyed and re-rendered?

Have a nice day
Yaisog

Correct.

Unless the \define is in another tiddler, I don’t see how it can change without re-rendering the entire tiddler anyway.

If the macro is defined in another tiddler and either global or imported using $importvariables then yes, any change in it will refresh the entire tiddler it is being imported into.

Keep in mind that macros return strings, i.e. the wikitext is not processed. So if you are not using string substitution in your macro, you never have to worry about it changing.