mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-06 17:05:34 +00:00
Summary of these changes: Two targeted improvements; more optimization is possible but IMO this is a significant enough improvement to go through test: 1. In oskView.ts, avoid calling `VisualKeyboard.refreshLayout` twice from `OSKView.refreshLayout`. This roughly halves the time spent in `VisualKeyboard.refreshLayout` for a keyboard switch. 2. In modelManager.ts, we avoid a very expensive `unloadModel` / `loadModel` sequence from `registerModel` by checking to see if the model spec we are passed is already registered. This avoids an expensive callback to Keyman for Android which causes the banner to 'bounce'. Longer notes: So `VisualKeyboard.refreshLayout` is called 20+ times during a keyboard switch. It is a very expensive call. It is the bulk of the time spent when you press the globe key in Keyman for Android. This seems ripe for optimization; see the following functions in keyboard.html: * `setKeymanLanguage` -> `setActiveKeyboard` -> 6 calls * `enableSuggestions` -> `registerModel` -> 8 calls * `stateChange` -> 6+ calls `OSKView.refreshLayout` always seems to call `VisualKeyboard.refreshLayout` twice: first with `setSize`, and then again itself. Keyman for Android and KeymanWeb seem to fight over who is responsible for selecting models (KMW first calls `loadModel`, then Keyman for Android calls `unloadModel`, `loadModel`, through `enableSuggestions`). The fix for rotation (`correctOSKTextSize()`), as called from `stateChange()`, is expensive as well, because it rebuilds the entire keyboard from scratch. Along with that, we have the size of the webview changing as the model is unloaded and reloaded and lots of back-and-forth between the KeymanWeb and KMEA which seems unnecessary. Shouldn't KeymanWeb be responsible for loading and unloading models, once they are all registered? We should only need to register the models once at page load time (when config changes, do we just reload the page?).
100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
// Defines the KeyboardManager and its related types.
|
|
///<reference path="../../keyboards/kmwkeyboards.ts" />
|
|
|
|
namespace com.keyman.text.prediction {
|
|
export class ModelManager {
|
|
// Tracks registered models by ID.
|
|
private registeredModels: {[id: string]: ModelSpec} = {};
|
|
|
|
// Allows for easy model lookup by language code; useful when switching keyboards.
|
|
languageModelMap: {[language:string]: ModelSpec} = {};
|
|
|
|
init() {
|
|
let keyman = com.keyman.singleton;
|
|
|
|
// Registers this module for keyboard (language) and model change events.
|
|
keyman['addEventListener']('keyboardchange', this.onKeyboardChange.bind(this));
|
|
}
|
|
|
|
onKeyboardChange(kbdInfo?: keyboards.KeyboardChangeData | string) {
|
|
let keyman = com.keyman.singleton;
|
|
let core = keyman.core;
|
|
|
|
if(typeof kbdInfo == 'string') { // This case refers to the active language code.
|
|
kbdInfo = {
|
|
['internalName']: keyman.keyboardManager.getActiveKeyboardName(),
|
|
['languageCode']: kbdInfo,
|
|
['indirect']: true
|
|
}
|
|
}
|
|
|
|
let lgCode = kbdInfo['languageCode'];
|
|
let model = this.languageModelMap[lgCode];
|
|
var loadPromise: Promise<void>;
|
|
|
|
if(core.activeModel) {
|
|
core.languageProcessor.unloadModel();
|
|
}
|
|
|
|
if(model) {
|
|
loadPromise = core.languageProcessor.loadModel(model);
|
|
}
|
|
}
|
|
|
|
// Accessible publicly as keyman.modelManager.register(model: ModelSpec)
|
|
register(model: ModelSpec): void {
|
|
let keyman = com.keyman.singleton;
|
|
let activeLanguage = keyman.keyboardManager.getActiveLanguage();
|
|
|
|
if(JSON.stringify(model) == JSON.stringify(this.registeredModels[model.id])) {
|
|
// We are already registered, let's not go through and re-register
|
|
// because we'll already have the correct model active
|
|
return;
|
|
}
|
|
this.registeredModels[model.id] = model;
|
|
|
|
// Register the model for each targeted language code variant.
|
|
let mm = this;
|
|
model.languages.forEach(function(code: string) {
|
|
mm.languageModelMap[code] = model;
|
|
|
|
// The model's for our active language! Activate it!
|
|
if(code == activeLanguage) {
|
|
// Manually trigger our model-update event function.
|
|
mm.onKeyboardChange(code);
|
|
}
|
|
});
|
|
}
|
|
|
|
deregister(modelId: string): void {
|
|
let keyman = com.keyman.singleton;
|
|
let core = keyman.core;
|
|
let model: ModelSpec;
|
|
|
|
// Remove the model from the id-lookup associative array.
|
|
if(this.registeredModels[modelId]) {
|
|
model = this.registeredModels[modelId];
|
|
delete this.registeredModels[modelId];
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// Is it the active model?
|
|
if(core.activeModel && core.activeModel.id == modelId) {
|
|
core.languageProcessor.unloadModel();
|
|
}
|
|
|
|
// Ensure the model is deregistered for each targeted language code variant.
|
|
let mm = this;
|
|
model.languages.forEach(function(code: string) {
|
|
if(mm.languageModelMap[code].id == modelId) {
|
|
delete mm.languageModelMap[code];
|
|
}
|
|
});
|
|
}
|
|
|
|
isRegistered(model: ModelSpec): boolean {
|
|
return !! this.registeredModels[model.id];
|
|
}
|
|
}
|
|
}
|