Improving reliability of threaded rendering, separating JS Utils into separate files.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Adsooi 2024-10-28 18:45:34 +01:00
parent 49e94317d4
commit 687b14429a
Signed by: Ad5001
GPG key ID: EF45F9C6AFE20160
18 changed files with 285 additions and 197 deletions

View file

@ -19,7 +19,7 @@
import Objects from "../module/objects.mjs" import Objects from "../module/objects.mjs"
import Latex from "../module/latex.mjs" import Latex from "../module/latex.mjs"
import * as MathLib from "../math/index.mjs" import * as MathLib from "../math/index.mjs"
import { escapeHTML } from "../utils.mjs" import { escapeHTML } from "../utils/index.mjs"
import { Action } from "./common.mjs" import { Action } from "./common.mjs"
/** /**

View file

@ -18,10 +18,11 @@
import js from "./lib/polyfills/js.mjs" import js from "./lib/polyfills/js.mjs"
import * as Modules from "./module/index.mjs" export * as Utils from "./utils/index.mjs"
import * as ObjsAutoload from "./objs/autoload.mjs" import * as ObjsAutoload from "./objs/autoload.mjs"
export * as Modules from "./module/index.mjs"
export * as MathLib from "./math/index.mjs" export * as MathLib from "./math/index.mjs"
export * as HistoryLib from "./history/index.mjs" export * as HistoryLib from "./history/index.mjs"
export * as Parsing from "./parsing/index.mjs" export * as Parsing from "./parsing/index.mjs"
export * as Utils from "./utils.mjs"

View file

@ -17,11 +17,11 @@
*/ */
import * as Utils from "../utils.mjs" import * as Utils from "../utils/index.mjs"
import { ExprEvalExpression } from "../lib/expr-eval/expression.mjs"
import Latex from "../module/latex.mjs" import Latex from "../module/latex.mjs"
import ExprParser from "../module/expreval.mjs" import ExprParser from "../module/expreval.mjs"
import Objects from "../module/objects.mjs" import Objects from "../module/objects.mjs"
import { ExprEvalExpression } from "../lib/expr-eval/expression.mjs"
const NUMBER_MATCHER = /^\d*\.\d+(e[+-]\d+)?$/ const NUMBER_MATCHER = /^\d*\.\d+(e[+-]\d+)?$/

View file

@ -17,7 +17,7 @@
*/ */
import * as Expr from "./expression.mjs" import * as Expr from "./expression.mjs"
import * as Utils from "../utils.mjs" import * as Utils from "../utils/index.mjs"
import Latex from "../module/latex.mjs" import Latex from "../module/latex.mjs"
import Objects from "../module/objects.mjs" import Objects from "../module/objects.mjs"
import ExprParser from "../module/expreval.mjs" import ExprParser from "../module/expreval.mjs"

View file

@ -18,7 +18,7 @@
import { Module } from "./common.mjs" import { Module } from "./common.mjs"
import { CanvasInterface, DialogInterface } from "./interface.mjs" import { CanvasInterface, DialogInterface } from "./interface.mjs"
import { textsup } from "../utils.mjs" import { textsup } from "../utils/index.mjs"
import { Expression } from "../math/index.mjs" import { Expression } from "../math/index.mjs"
import Latex from "./latex.mjs" import Latex from "./latex.mjs"
import Objects from "./objects.mjs" import Objects from "./objects.mjs"

View file

@ -97,6 +97,7 @@ class LatexAPI extends Module {
* true if latex has been enabled by the user, false otherwise. * true if latex has been enabled by the user, false otherwise.
*/ */
this.enabled = false this.enabled = false
this.promises = new Set()
} }
/** /**
@ -139,9 +140,12 @@ class LatexAPI extends Module {
if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!") if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!")
let render let render
if(this.#latex.supportsAsyncRender) { if(this.#latex.supportsAsyncRender) {
console.trace()
this.emit(new AsyncRenderStartedEvent(markup, fontSize, color)) this.emit(new AsyncRenderStartedEvent(markup, fontSize, color))
render = await this.#latex.renderAsync(markup, fontSize, color) // Storing promise so that it does not get dereferenced.
const promise = this.#latex.renderAsync(markup, fontSize, color)
this.promises.add(promise)
render = await promise
this.promises.delete(promise)
this.emit(new AsyncRenderFinishedEvent(markup, fontSize, color)) this.emit(new AsyncRenderFinishedEvent(markup, fontSize, color))
} else { } else {
render = this.#latex.renderSync(markup, fontSize, color) render = this.#latex.renderSync(markup, fontSize, color)

View file

@ -17,7 +17,7 @@
*/ */
import { Module } from "./common.mjs" import { Module } from "./common.mjs"
import { textsub } from "../utils.mjs" import { textsub } from "../utils/index.mjs"
class ObjectsAPI extends Module { class ObjectsAPI extends Module {

View file

@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { getRandomColor } from "../utils.mjs"
import Objects from "../module/objects.mjs" import Objects from "../module/objects.mjs"
import Latex from "../module/latex.mjs" import Latex from "../module/latex.mjs"
import { getRandomColor } from "../utils/index.mjs"
import { ensureTypeSafety, serializesByPropertyType } from "../parameters.mjs" import { ensureTypeSafety, serializesByPropertyType } from "../parameters.mjs"
// This file contains the default data to be imported from all other objects // This file contains the default data to be imported from all other objects

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { textsub } from "../utils.mjs" import { textsub } from "../utils/index.mjs"
import Objects from "../module/objects.mjs" import Objects from "../module/objects.mjs"
import { ExecutableObject } from "./common.mjs" import { ExecutableObject } from "./common.mjs"
import { parseDomain, Expression, SpecialDomain } from "../math/index.mjs" import { parseDomain, Expression, SpecialDomain } from "../math/index.mjs"

View file

@ -16,151 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
// Add string methods import { textsub, textsup } from "./subsup.mjs"
/**
* Replaces latin characters with their uppercase versions.
* @return {string}
*/
String.prototype.toLatinUppercase = function() {
return this.replace(/[a-z]/g, function(match) {
return match.toUpperCase()
})
}
/**
* Removes the first and last character of a string
* Used to remove enclosing characters like quotes, parentheses, brackets...
* @note Does NOT check for their existence ahead of time.
* @return {string}
*/
String.prototype.removeEnclosure = function() {
return this.substring(1, this.length - 1)
}
/**
* Rounds to a certain number of decimal places.
* From https://stackoverflow.com/a/48764436
*
* @param {number} decimalPlaces
* @return {number}
*/
Number.prototype.toDecimalPrecision = function(decimalPlaces = 0) {
const p = Math.pow(10, decimalPlaces)
const n = (this * p) * (1 + Number.EPSILON)
return Math.round(n) / p
}
const CHARACTER_TO_POWER = new Map([
["-", "⁻"],
["+", "⁺"],
["=", "⁼"],
[" ", ""],
["(", "⁽"],
[")", "⁾"],
["0", "⁰"],
["1", "¹"],
["2", "²"],
["3", "³"],
["4", "⁴"],
["5", "⁵"],
["6", "⁶"],
["7", "⁷"],
["8", "⁸"],
["9", "⁹"],
["a", "ᵃ"],
["b", "ᵇ"],
["c", "ᶜ"],
["d", "ᵈ"],
["e", "ᵉ"],
["f", "ᶠ"],
["g", "ᵍ"],
["h", "ʰ"],
["i", "ⁱ"],
["j", "ʲ"],
["k", "ᵏ"],
["l", "ˡ"],
["m", "ᵐ"],
["n", "ⁿ"],
["o", "ᵒ"],
["p", "ᵖ"],
["r", "ʳ"],
["s", "ˢ"],
["t", "ᵗ"],
["u", "ᵘ"],
["v", "ᵛ"],
["w", "ʷ"],
["x", "ˣ"],
["y", "ʸ"],
["z", "ᶻ"]
])
const CHARACTER_TO_INDICE = new Map([
["-", "₋"],
["+", "₊"],
["=", "₌"],
["(", "₍"],
[")", "₎"],
[" ", ""],
["0", "₀"],
["1", "₁"],
["2", "₂"],
["3", "₃"],
["4", "₄"],
["5", "₅"],
["6", "₆"],
["7", "₇"],
["8", "₈"],
["9", "₉"],
["a", "ₐ"],
["e", "ₑ"],
["h", "ₕ"],
["i", "ᵢ"],
["j", "ⱼ"],
["k", "ₖ"],
["l", "ₗ"],
["m", "ₘ"],
["n", "ₙ"],
["o", "ₒ"],
["p", "ₚ"],
["r", "ᵣ"],
["s", "ₛ"],
["t", "ₜ"],
["u", "ᵤ"],
["v", "ᵥ"],
["x", "ₓ"]
])
const EXPONENTS = [
"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"
]
const EXPONENTS_REG = new RegExp("([" + EXPONENTS.join("") + "]+)", "g")
/**
* Put a text in sup position
* @param {string} text
* @return {string}
*/
export function textsup(text) {
let ret = ""
text = text.toString()
for(let letter of text)
ret += CHARACTER_TO_POWER.has(letter) ? CHARACTER_TO_POWER.get(letter) : letter
return ret
}
/**
* Put a text in sub position
* @param {string} text
* @return {string}
*/
export function textsub(text) {
let ret = ""
text = text.toString()
for(let letter of text)
ret += CHARACTER_TO_INDICE.has(letter) ? CHARACTER_TO_INDICE.get(letter) : letter
return ret
}
/** /**
* Simplifies (mathematically) a mathematical expression. * Simplifies (mathematically) a mathematical expression.
@ -400,35 +256,3 @@ export function parseName(str, removeUnallowed = true) {
return str return str
} }
/**
* Creates a randomized color string.
* @returns {string}
*/
export function getRandomColor() {
let clrs = "0123456789ABCDEF"
let color = "#"
for(let i = 0; i < 6; i++) {
color += clrs[Math.floor(Math.random() * (16 - 5 * (i % 2 === 0)))]
}
return color
}
/**
* Escapes text to html entities.
* @param {string} str
* @returns {string}
*/
export function escapeHTML(str) {
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
}
/**
* Parses exponents and replaces them with expression values
* @param {string} expression - The expression to replace in.
* @return {string} The parsed expression
*/
export function exponentsToExpression(expression) {
return expression.replace(EXPONENTS_REG, (m, exp) => "^" + exp.split("").map((x) => EXPONENTS.indexOf(x)).join(""))
}

View file

@ -0,0 +1,22 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export * from "./prototype.mjs"
export * from "./subsup.mjs"
export * from "./expression.mjs"
export * from "./other.mjs"

View file

@ -0,0 +1,41 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Creates a randomized color string.
* @returns {string}
*/
export function getRandomColor() {
let clrs = "0123456789ABCDEF"
let color = "#"
for(let i = 0; i < 6; i++) {
color += clrs[Math.floor(Math.random() * (16 - 5 * (i % 2 === 0)))]
}
return color
}
/**
* Escapes text to html entities.
* @param {string} str
* @returns {string}
*/
export function escapeHTML(str) {
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
}

View file

@ -0,0 +1,51 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Add string methods
/**
* Replaces latin characters with their uppercase versions.
* @return {string}
*/
String.prototype.toLatinUppercase = function() {
return this.replace(/[a-z]/g, function(match) {
return match.toUpperCase()
})
}
/**
* Removes the first and last character of a string
* Used to remove enclosing characters like quotes, parentheses, brackets...
* @note Does NOT check for their existence ahead of time.
* @return {string}
*/
String.prototype.removeEnclosure = function() {
return this.substring(1, this.length - 1)
}
/**
* Rounds to a certain number of decimal places.
* From https://stackoverflow.com/a/48764436
*
* @param {number} decimalPlaces
* @return {number}
*/
Number.prototype.toDecimalPrecision = function(decimalPlaces = 0) {
const p = Math.pow(10, decimalPlaces)
const n = (this * p) * (1 + Number.EPSILON)
return Math.round(n) / p
}

140
common/src/utils/subsup.mjs Normal file
View file

@ -0,0 +1,140 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const CHARACTER_TO_POWER = new Map([
["-", "⁻"],
["+", "⁺"],
["=", "⁼"],
[" ", ""],
["(", "⁽"],
[")", "⁾"],
["0", "⁰"],
["1", "¹"],
["2", "²"],
["3", "³"],
["4", "⁴"],
["5", "⁵"],
["6", "⁶"],
["7", "⁷"],
["8", "⁸"],
["9", "⁹"],
["a", "ᵃ"],
["b", "ᵇ"],
["c", "ᶜ"],
["d", "ᵈ"],
["e", "ᵉ"],
["f", "ᶠ"],
["g", "ᵍ"],
["h", "ʰ"],
["i", "ⁱ"],
["j", "ʲ"],
["k", "ᵏ"],
["l", "ˡ"],
["m", "ᵐ"],
["n", "ⁿ"],
["o", "ᵒ"],
["p", "ᵖ"],
["r", "ʳ"],
["s", "ˢ"],
["t", "ᵗ"],
["u", "ᵘ"],
["v", "ᵛ"],
["w", "ʷ"],
["x", "ˣ"],
["y", "ʸ"],
["z", "ᶻ"]
])
const CHARACTER_TO_INDICE = new Map([
["-", "₋"],
["+", "₊"],
["=", "₌"],
["(", "₍"],
[")", "₎"],
[" ", ""],
["0", "₀"],
["1", "₁"],
["2", "₂"],
["3", "₃"],
["4", "₄"],
["5", "₅"],
["6", "₆"],
["7", "₇"],
["8", "₈"],
["9", "₉"],
["a", "ₐ"],
["e", "ₑ"],
["h", "ₕ"],
["i", "ᵢ"],
["j", "ⱼ"],
["k", "ₖ"],
["l", "ₗ"],
["m", "ₘ"],
["n", "ₙ"],
["o", "ₒ"],
["p", "ₚ"],
["r", "ᵣ"],
["s", "ₛ"],
["t", "ₜ"],
["u", "ᵤ"],
["v", "ᵥ"],
["x", "ₓ"]
])
const EXPONENTS = [
"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"
]
const EXPONENTS_REG = new RegExp("([" + EXPONENTS.join("") + "]+)", "g")
/**
* Put a text in sup position
* @param {string} text
* @return {string}
*/
export function textsup(text) {
let ret = ""
text = text.toString()
for(let letter of text)
ret += CHARACTER_TO_POWER.has(letter) ? CHARACTER_TO_POWER.get(letter) : letter
return ret
}
/**
* Put a text in sub position
* @param {string} text
* @return {string}
*/
export function textsub(text) {
let ret = ""
text = text.toString()
for(let letter of text)
ret += CHARACTER_TO_INDICE.has(letter) ? CHARACTER_TO_INDICE.get(letter) : letter
return ret
}
/**
* Parses exponents and replaces them with expression values
* @param {string} expression - The expression to replace in.
* @return {string} The parsed expression
*/
export function exponentsToExpression(expression) {
return expression.replace(EXPONENTS_REG, (m, exp) => "^" + exp.split("").map((x) => EXPONENTS.indexOf(x)).join(""))
}

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { textsup, textsub, parseName, getRandomColor, escapeHTML, exponentsToExpression } from "../../src/utils.mjs" import { textsup, textsub, parseName, getRandomColor, escapeHTML, exponentsToExpression } from "../../src/utils/index.mjs"
import { describe, it } from "mocha" import { describe, it } from "mocha"

View file

@ -178,4 +178,4 @@ describe("Math/Expression", function() {
expect(() => executeExpression("x+n")).to.throw("Undefined variable n.") expect(() => executeExpression("x+n")).to.throw("Undefined variable n.")
}) })
}) })
}) })

View file

@ -21,9 +21,10 @@ from platform import system as os_name, release as OS_RELEASE
from sys import path as sys_path from sys import path as sys_path
from sys import argv, exit from sys import argv, exit
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from math import ceil
from time import time from time import time
from PySide6.QtCore import QTranslator, QLocale from PySide6.QtCore import QTranslator, QLocale, QThreadPool, QThread
from PySide6.QtGui import QIcon from PySide6.QtGui import QIcon
from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQuickControls2 import QQuickStyle from PySide6.QtQuickControls2 import QQuickStyle
@ -162,7 +163,11 @@ def run():
dep_time = time() dep_time = time()
print("Loaded dependencies in " + str((dep_time - start_time) * 1000) + "ms.") print("Loaded dependencies in " + str((dep_time - start_time) * 1000) + "ms.")
# Maxing thread count to half the computer's thread count to avoid maxing CPU
# with too many threads (and also leaving some for rendering).
QThreadPool.globalInstance().setMaxThreadCount(int(ceil(QThread.idealThreadCount() / 2)))
register_icon_directories() register_icon_directories()
app = create_qapp() app = create_qapp()
translator = install_translation(app) translator = install_translation(app)

View file

@ -22,6 +22,8 @@ from PySide6.QtQml import QJSValue
from LogarithmPlotter.util.js import PyJSValue from LogarithmPlotter.util.js import PyJSValue
NO_RETURN = [None, QJSValue.SpecialValue.UndefinedValue]
def check_callable(function: Callable|QJSValue) -> Callable|None: def check_callable(function: Callable|QJSValue) -> Callable|None:
""" """
@ -153,13 +155,12 @@ class PyPromise(QObject):
@Slot(QObject) @Slot(QObject)
def _fulfill(self, data): def _fulfill(self, data):
self._state = "fulfilled" self._state = "fulfilled"
no_return = [None, QJSValue.SpecialValue.UndefinedValue]
print("Finished", self._runner.args) print("Finished", self._runner.args)
for i in range(len(self._fulfills)): for i in range(len(self._fulfills)):
try: try:
result = self._fulfills[i](data) result = self._fulfills[i](data)
result = result.qjs_value if isinstance(result, PyJSValue) else result result = result.qjs_value if isinstance(result, PyJSValue) else result
data = result if result not in no_return else data # Forward data. data = result if result not in NO_RETURN else data # Forward data.
except Exception as e: except Exception as e:
self._reject(repr(e), start_at=i) self._reject(repr(e), start_at=i)
break break
@ -168,8 +169,7 @@ class PyPromise(QObject):
@Slot(str) @Slot(str)
def _reject(self, error, start_at=0): def _reject(self, error, start_at=0):
self._state = "rejected" self._state = "rejected"
no_return = [None, QJSValue.SpecialValue.UndefinedValue]
for i in range(start_at, len(self._rejects)): for i in range(start_at, len(self._rejects)):
result = self._rejects[i](error) result = self._rejects[i](error)
result = result.qjs_value if isinstance(result, PyJSValue) else result result = result.qjs_value if isinstance(result, PyJSValue) else result
error = result if result not in no_return else error # Forward data. error = result if result not in NO_RETURN else error # Forward data.