Our pure JavaScript Scheduler component


Post by odiyaerlichster »

Hi,
We have recently started noticing a significant decrease in performance with a large number of tasks (around 300 and up) when using a custom eventRenderer function returning JSX.
We have tried just returning the simplest JSX for example:

const eventRenderer=({eventRecord,assignmentRecord})=>{
return <div key={assignmentRecord.id}> {eventRecord.name} </div>
}

And there is still a significant performance decline.
When we don't use a custom eventRenderer function, the performance is almost not affected at all.
We are unsure if this is related to a change in our code recently or that this problem has always existed.
We tested the example on the website with the large dataSet and a custom eventRenderer and it works very well, which is why we aren't sure where exactly the problem lays.
I'm attaching a snippet of our code with the config options so you can help us detect where our issue is:

<BryntumSchedulerPro
      startDate={startDate}
      ref={ref}
      onScroll={onVerticalScroll}
      scrollable={!disableScroll}
      onVisibleDateRangeChange={onVisibleDateRangeChange}
      listeners={listeners}
      project={project}
      columns={columns}
      calendars={[calendar] as any}
      weekStartDay={weekStartDay}
      events={events}
      resources={resources}
      assignments={assignments}
      dependencies={dependencies}
      presets={presets}
      eventRenderer={eventRenderer}
      regionResizeFeature={{
        showSplitterButtons: false,
      }}
      treeFeature
      eventDragCreateFeature={false}
      eventTooltipFeature={eventTooltipFeature}
      cellTooltipFeature={false}
      scheduleTooltipFeature={false}
      managedEventSizing={false}
      showCreationTooltip={false}
      taskEditFeature={false}
      multiEventSelect
      createEventOnDblClick={false}
      enableEventAnimations={false}
      cellEditFeature={false}
      eventResizeFeature={{ showTooltip: false, lockLayout: true }}
      panFeature={panFeature}
      eventDragSelectFeature={{ disabled: false }}
      eventDragFeature={eventDragFeature}
      nestedEventsFeature={{
        headerHeight: rowHeight,
        eventHeight: rowHeight,
        resourceMargin: SWIMLANE_SUB_TASKS_MARGIN,
        barMargin: SWIMLANE_SUB_TASKS_MARGIN,
        eventLayout: 'stack',
        constrainResizeToParent: false,
        constrainDragToParent: false,
      }}
      barMargin={SWIMLANE_EVENT_MARGIN}
      resourceMargin={SWIMLANE_EVENT_MARGIN}
      infiniteScroll
      eventStyle={'colored'}
      eventColor={'red'}
      eventLayout={eventLayout}
      milestoneTextPosition="always-outside"
      timeRanges={timeRanges as TimeSpan[] | Partial<TimeSpanConfig>[]}
      timeRangesFeature={timeRangesFeature}
      dependenciesFeature={dependenciesFeature}
      timeSelectionFeature={false}
      scheduleContextFeature={false}
      scheduleMenuFeature={false}
      timeAxisHeaderMenuFeature={false}
      resourceMenuFeature={false}
      timeResolution={{ unit: 'day', increment: 1 }}
      useInitialAnimation={false}
      zoomKeepsOriginalTimespan
      zoomOnMouseWheel={false}
      cellMenuFeature={false}
      nonWorkingTimeFeature={{
        hideRangesOnZooming: false,
        maxTimeAxisUnit: MAX_TIME_AXIS_UNIT_FOR_NON_WORKING_DAYS,
        showHeaderElements: false,
      }}
      zoomOnTimeAxisDoubleClick={false}
      stickyHeaders
      enableDeleteKey={false}
      enableUndoRedoKeys={false}
      eventCopyPasteFeature={false}
      rowCopyPasteFeature={false}
      cellCopyPasteFeature={false}
      emptyText={emptyText}
      cycleResolutionPopupClass={CustomCycleResolutionPopup}
      schedulingIssueResolutionPopupClass={CustomSchedulingIssueResolutionPopup}
      narrowEventWidth={SWIMLANE_TEXT_INPUT_NARROW_EVENT_WIDTH}
      subGridConfigs={subGridConfigs}
      onScheduleClick={onScheduleClick}
      onEventClick={onEventClick}
      onEventDblClick={onEventDblClick}
      onScheduleDblClick={onScheduleDblClick}
      onScheduleMouseEnter={onScheduleMouseEnter}
      onScheduleMouseLeave={onScheduleMouseLeave}
      onBeforeEventDropFinalize={onBeforeEventDropFinalize}
      onBeforeEventResizeFinalize={onBeforeEventResizeFinalize}
      onEventMenuBeforeShow={onEventMenuBeforeShow}
      onPresetChange={onPresetChange}
      onEventSelectionChange={onEventSelectionChange}
      onBeforeEventDrag={onBeforeEventDrag}
      onEventDrag={onEventDrag}
      onEventDragStart={onEventDragStart}
      onAfterEventDrop={onAfterEventDrop}
      onEventMouseEnter={onEventMouseEnter}
      onEventMouseLeave={onEventMouseLeave}
      onEventResizeStart={onEventResizeStart}
      onEventPartialResize={onEventPartialResize}
      onEventResizeEnd={onEventResizeEnd}
      onTimelineViewportResize={onTimelineViewportResize}
      onDependencyClick={onDependencyClick}
      onExpandNode={onExpandNode}
      onCollapseNode={onCollapseNode}
      onDependenciesDrawn={onDependenciesDrawn}
      onBeforeDependencyCreateDrag={onBeforeDependencyCreateDrag}
      onAfterDependencyCreateDrop={onAfterDependencyCreateDrop}
      keyMap={
        {
          space: null,
        } as any
      }
      suppressFit
    />

I'm also attaching a video for reference that shows the decrease in performance when using a custom simple eventRenderer:

Screen Recording 2024-06-05 at 14.17.10.mov
(3.85 MiB) Downloaded 2 times

And a reference without a custom eventRenderer:

Screen Recording 2024-06-05 at 14.15.20.mov
(3.85 MiB) Downloaded 1 time

We'd greatly appreciate it if you could take a look and let us know if there is an issue with one of our configs or if the eventRenderer function.

Thanks,
Odiya


Post by mats »

This it to be expected as you enter into the whole React life cycle once per event. General performance rule: if you want fast rendering, do as little as possible in the render loop. Do you actually need React? Or can you use simple HTML/CSS?


Post by odiyaerlichster »

Hi Mats,
Just following up to see if you had a chance to look further into this issue.
I see there is another forum post reporting the same issue: viewtopic.php?p=148599 and that an issue was opened to investigate it, do you have an estimation when this will be resolved?
This is currently majorly affecting us and our customers, blocking them from being able to use our product.
Thanks,
Odiya


Post by marcio »

Hey Odiya,

Thanks for reaching out.

Unfortunately, there is no updates, but I let our team know if we can improve the priority of that ticket to identify and provide details of what could be done to not have that performance issue.

Best regards,
Márcio


Post by mykels »

Hi Marcio,

Thank you for your response. I appreciate your team's efforts in maintaining the Bryntum Scheduler Pro.

I've conducted a thorough profiling on the Bryntum Scheduler Pro components and I've noticed a significant performance degradation since the introduction of flushSync usage. This issue seems to occur only when rendering as a JSX component for the event.

In this case, it seems that flushSync was introduced to solve the flickering issue. While it might have solved that problem, it introduced another one - slowness in various scheduler interactions like scroll, zoom, etc. This is likely due to the blocking of the main thread and inefficient rendering caused by the usage of the flushSync API.

I've attached a video and my profiler project demonstrating the differences. I believe these findings will provide valuable insights into the performance issues we're experiencing.

I understand that the warning is currently considered non-critical, but I believe addressing this performance issue could greatly improve the user experience for us and other users of the Bryntum Scheduler Pro.

I look forward to your thoughts on this matter.

Best regards,
Micha

Attachments
Screen Recording 2024-07-01 at 9.34.23.mov
(26.57 MiB) Downloaded 1 time
acs-bryntum-profiler.zip
(12.32 MiB) Downloaded 1 time

Post by mats »

The real question here is where the CPU cycles are being consumed, is it in your JSX code or in React core code? Our rendering needs to be synchronous in order to measure and set row height correctly.


Post by mykels »

I believe it's in the react core code (in the case of the profiler example).

As you can see in the project - the JSX component is as simple as possible (much simpler than the real use case in our own application).

I'm sure there are alternative solutions for this.


Post by marcio »

Hey mykels,

We're looking into this for possible solutions on this. We'll keep you updated but it could take some time as it'll need some debugging and testing on this issue.

Best regards,
Márcio


Post by mykels »

Thanks marcio,

Please keep us posted as this is a critical issue for our customers.

Best regards,
Micha


Post Reply