Kahuna:
I discovered two things - first if the records have been touched (selected) in the table - regardless of their position in the table then they can be quickly re-selected (guess the data is then in memory), so perhaps some method to quickly step through all of the records in code before display in the table could speed the apparent UI selection.
This is correct. Servoy “lazy loads” stuff as needed. This includes forms (first time you hit a form it is loaded into memory) and records (first time you hit a record all of it’s related data is loaded). Related data is loaded if a related field is showing, you have a calculation referencing a relation, and/or if you have code referencing a relation on a form event that fires when the record is selected (onRecordSelection being the most obvious one).
There are ways to preload both forms and data. Form preloading is fairly well discussed around the forum and if I remember correctly, Scott’s site has an article on it.
Keep in mind Servoy “garbage collects” (dumps out of memory) unused objects and data. You have very little control when this process occurs. You can only increase the client heap size in the admin application. At the default “servoy.maxClientHeap=64mb” there is a hard cap of how many forms Servoy keeps in memory (~30?). At “servoy.maxClientHeap=128mb” the hard cap is around 75 forms (I’m going on 2+ year old memory here so Johann please correct!). Increasing the servoy.maxClientHeap further, Servoy holds as many forms and layout objects in memory as the size allows.
Data that is shown in the client is also somewhat affected by the servoy.maxClientHeap. But there is no hard rules for how long it stays that we’ve figured out. You can have the servoy.maxClientHeap set very high – do nothing on the client for 30 seconds – and some of the records showing will still reload when you next click.
Kahuna:
Secondly - if I use our preferred filtering tool, which is the TableFilterParam, the the entire table becomes slow - not just the >200 set. I assume because none of the filtered records were in the initial <200 set! Again - perhaps a code step through the filtered set might speed the apparent UI selection?
Johann answered this one. Filtering just adds a WHERE clause to the queries – need to take that into account when indexing. Performance tab will show you what needs to be done.
Kahuna:
Was your presentation recorded - I’d love to look more deeply into your comments.
Sorry, no recording. But we could present it again if Servoy is up for putting it on their schedule and hosting it. Maybe use your solution as examples. Get a few other people to throw their thoughts and experiences in. Big round table discussion type deal.
Kahuna:
Apart from the obvious indexing aspects - in this instance (I know you only have the barest of info on the solution) where would you suggest we begin to address the speed issue?
1. Basic. The design pattern of your layouts. When and where you show various related data. Move dynamic calculations to “static calcs”. Move from showing data through relations to selectively controlling per event. Remove duplicate and unused relationships and calculations. Remove duplicate code firing on multiple form events (ie. onLoad, onShow…then finally onRecordSelection). Calculate all client state information only once for the session and only modify as it changes (i.e. we’ve seen the navigation scheme get recalculated for every form navigated to).
2. Advanced. Base complex layouts on views (put calcs in the view), custom queries (bring back complex related data in one shot), solution model (show what is needed on the fly based on the situation). Store complex data locally JSONed objects in the “.Servoy” directory (if the object is garbage collected, load it from the client machine instead of going back to the server). Go completely nuts like the adBlocks guys and do a pixel-perfect rendition of a page of data in html and page it all yourself.
3. In addition to the performance tab, speed test everything with a timer:
/*
* TITLE : CALLBACK_timer
*
* MODULE : rsrc_CODE_frameworks
*
* ABOUT : starts/stops timer for debugging speed issues
*
* INPUT : 1- 'start'/'stop'
*
* OUTPUT :
*
* REQUIRES : solutionPrefs
*
* USAGE : CALLBACK_timer(startStop) Starts/stops timer. Displays value in status area when stopped
*
* MODIFIED : July 21, 2008 -- Troy Elliott, Data Mosaic
*
*/
var startStop = arguments[0]
//check for solutionPrefs
if (!application.__parent__.solutionPrefs) {
solutionPrefs = {config : {timer: new Object()}}
}
//check for config node
else if (!solutionPrefs.config) {
solutionPrefs.config = {timer : new Object()}
}
//check for timer node
else if (!solutionPrefs.config.timer) {
solutionPrefs.config.timer = new Object()
}
//start timing
if (startStop == 'start') {
solutionPrefs.config.timer.timeStart = new Date().getTime()
}
//stop timing
else if (startStop == 'stop') {
if (solutionPrefs.config.timer.timeStart) {
var endTime = solutionPrefs.config.timer.timeEnd = new Date().getTime()
var elapsed = solutionPrefs.config.timer.timeEnd - solutionPrefs.config.timer.timeStart
//only set when demo mode not expired
if (!solutionPrefs.config.demoModeExpired) {
application.setStatusText('Elapsed time is: '+ elapsed +' ms. Finished '+utils.dateFormat(endTime, 'H:MM:ss'))
}
}
else {
plugins.dialogs.showErrorDialog('Timer error','The timer has not been started yet')
}
}
4. Give user feedback
A second for something to happen isn’t so bad if you give the user feedback. We do this in several ways: turn the cursor into the the busy cursor (I think I posted a basic plugin for this and recently Patrick put one out that even works in webclient), show a busy progress bar (bar just spins), and/or a progressing progress bar (if it is a very long process).
/*
* TITLE : CALLBACK_progressbar_start
*
* MODULE : rsrc_CODE_frameworks
*
* ABOUT : sets up the progressbar
*
* INPUT : 1- initial value (usually 0); if null, bar will be indeterminate; if -273, use gif
* 2- explanation text; if empty, text will not be available
* 3- explanation toolTip
* 4- minimum (defaults to 0)
* 5- maximum (defaults to 100)
*
* OUTPUT :
*
* REQUIRES :
*
* USAGE : CALLBACK_progressbar_start(startPosition,explanation,explanationTooltip,minValue,maxValue)
*
* MODIFIED : June 26, 2008 -- Troy Elliott, Data Mosaic
*
*/
if (application.__parent__.solutionPrefs) {
var formName = 'TOOL_progress_bar'
var baseForm = solutionPrefs.config.formNameBase
var initialValue = arguments[0]
var explanationText = arguments[1]
var explanationToolTip = arguments[2]
var minimum = (typeof arguments[3] == 'number') ? arguments[3] : 0
var maximum = (typeof arguments[4] == 'number') ? arguments[4] : 100
//only run if progressbar not added
if (forms[baseForm + '__header'].elements.tab_toolbar.getTabFormNameAt(forms[baseForm + '__header'].elements.tab_toolbar.getMaxTabIndex()) != formName) {
//save down active toolbar to restore
solutionPrefs.config.lastSelectedToolbar = forms[baseForm + '__header'].elements.tab_toolbar.tabIndex
//load scrollbar tab
forms[baseForm + '__header'].elements.tab_toolbar.addTab(forms[formName],'')
forms[baseForm + '__header'].elements.tab_toolbar.tabIndex = forms[baseForm + '__header'].elements.tab_toolbar.getMaxTabIndex()
//hide toolbar controls
forms[baseForm + '__header'].elements.btn_toolbar_toggle.visible = false
forms[baseForm + '__header'].elements.btn_toolbar_popdown.visible = false
}
//hide bean stuff to make sure the passed values are obeyed
else {
forms[formName].elements.lbl_progress_text.visible = false
forms[formName].elements.bean_progress.indeterminate = false
forms[formName].elements.bean_progress.value = 0
forms[formName].elements.bean_progress.visible = false
forms[formName].elements.gfx_progress.visible = false
}
//turn on progressbar elements
forms[formName].elements.bean_progress.visible = true
forms[formName].elements.lbl_progress_text.visible = (explanationText) ? true : false
forms[formName].elements.gfx_progress.visible = false
//indeterminate gif
if (initialValue == -273) {
forms[formName].elements.bean_progress.visible = false
forms[formName].elements.gfx_progress.visible = true
}
//indeterminate scrollbar
else if (initialValue == null && typeof initialValue == 'object') {
forms[formName].elements.bean_progress.indeterminate = true
}
//normal scrollbar
else {
//initial value
if (typeof initialValue == 'number') {
forms[formName].elements.bean_progress.value = initialValue
}
else {
forms[formName].elements.bean_progress.value = 0
}
//min/max
forms[formName].elements.bean_progress.minimum = minimum
forms[formName].elements.bean_progress.maximum = maximum
}
//set text
if (explanationText) {
forms[formName].elements.lbl_progress_text.text = explanationText
forms[formName].elements.lbl_progress_text.toolTipText = explanationToolTip
}
//two updates (maybe more required)
application.updateUI()
application.updateUI()
application.updateUI()
}
/*
* TITLE : CALLBACK_progressbar_stop
*
* MODULE : rsrc_CODE_frameworks
*
* ABOUT : reset toolbars and select last toolbar user was viewing
*
* INPUT :
*
* OUTPUT :
*
* REQUIRES :
*
* USAGE : CALLBACK_progressbar_stop()
*
* MODIFIED : June 26, 2008 -- Troy Elliott, Data Mosaic
*
*/
if (application.__parent__.solutionPrefs) {
var baseForm = solutionPrefs.config.formNameBase
var formName = 'TOOL_progress_bar'
//remove progress toolbar if it is present
if (forms[baseForm + '__header'].elements.tab_toolbar.getTabFormNameAt(forms[baseForm + '__header'].elements.tab_toolbar.getMaxTabIndex()) == formName) {
forms[baseForm + '__header'].elements.tab_toolbar.removeTabAt(forms[baseForm + '__header'].elements.tab_toolbar.getMaxTabIndex())
//set toolbar to previous if it wasn't the last tab
if (solutionPrefs.config.lastSelectedToolbar != forms[baseForm + '__header'].elements.tab_toolbar.getMaxTabIndex()) {
globals.TOOLBAR_cycle(solutionPrefs.config.lastSelectedToolbar)
}
//show toolbar controls
forms[baseForm + '__header'].elements.btn_toolbar_toggle.visible = true
//forms[baseForm + '__header'].elements.btn_toolbar_popdown.visible = false
}
//clear out lastSelectedToolbar
solutionPrefs.config.lastSelectedToolbar = null
//hide bean stuff to be ready for next time
forms[formName].elements.lbl_progress_text.visible = false
forms[formName].elements.bean_progress.indeterminate = false
forms[formName].elements.bean_progress.value = 0
forms[formName].elements.bean_progress.visible = false
forms[formName].elements.gfx_progress.visible = false
}
I’ve attached a couple of pictures of the busy progress bar indicator flipping on when a fairly dense form is first loading. User feedback is so important we have our frameworks setup to be able to flip this on for each form in our navigation engine (“notifications” section, top right of screen shot). Additionally, the two callback functions above can be used in workflow code as needed.
Kahuna:
Its a hack I know, but would my thoughts on initialising the data with a code ‘step-thru’ help alleviate the situation without major restructuring of the way the forms are nested, and is it appropriate?
Unfortunately, Servoy’s default solution design approach easily creates query explosion situations when you start building data-dense/complex forms. The only easy and fast fixes are indexing, not showing calculations, and not showing data multiple relations deep. Squeezing more performance after this can be done but your first few attempts will be time consuming. But well worth the exploration.
/End brain dump ![Cool 8)]()