[apps/graph] Reworked function caching

Instead of being memoized all at once before display, functions values
are now stored at evaluation time. Fixed some quirks with caching
preparation.

Change-Id: I5d212c271c8c41a6dc9074a15c720f0bccf8ac40
This commit is contained in:
Gabriel Ozouf
2020-06-12 12:38:35 +02:00
committed by Émilie Feral
parent 42fcf557b8
commit 1bee23cf4f
8 changed files with 84 additions and 112 deletions

View File

@@ -16,7 +16,7 @@ public:
return recordSatisfyingTestAtIndex(i, &isFunctionActiveOfType, &plotType);
}
Shared::ExpiringPointer<Shared::ContinuousFunction> modelForRecord(Ion::Storage::Record record) const { return Shared::ExpiringPointer<Shared::ContinuousFunction>(static_cast<Shared::ContinuousFunction *>(privateModelForRecord(record))); }
Shared::ExpiringPointer<Shared::ContinuousFunctionCache> cacheAtIndex(int i) const { return (i < Shared::ContinuousFunctionCache::k_numberOfAvailableCaches) ? Shared::ExpiringPointer<Shared::ContinuousFunctionCache>(k_functionCaches + i) : nullptr; }
Shared::ContinuousFunctionCache * cacheAtIndex(int i) const { return (i < Shared::ContinuousFunctionCache::k_numberOfAvailableCaches) ? m_functionCaches + i : nullptr; }
Ion::Storage::Record::ErrorStatus addEmptyModel() override;
private:
const char * modelExtension() const override { return Ion::Storage::funcExtension; }
@@ -27,7 +27,7 @@ private:
return isFunctionActive(model, context) && plotType == static_cast<Shared::ContinuousFunction *>(model)->plotType();
}
mutable Shared::ContinuousFunction m_functions[k_maxNumberOfMemoizedModels];
mutable Shared::ContinuousFunctionCache k_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches];
mutable Shared::ContinuousFunctionCache m_functionCaches[Shared::ContinuousFunctionCache::k_numberOfAvailableCaches];
};

View File

@@ -1,6 +1,7 @@
#include "graph_view.h"
#include "../app.h"
#include <assert.h>
#include <algorithm>
using namespace Shared;
@@ -28,7 +29,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
for (int i = 0; i < activeFunctionsCount ; i++) {
Ion::Storage::Record record = functionStore->activeRecordAtIndex(i);
ExpiringPointer<ContinuousFunction> f = functionStore->modelForRecord(record);
ExpiringPointer<ContinuousFunctionCache> cch = functionStore->cacheAtIndex(i);
ContinuousFunctionCache * cch = functionStore->cacheAtIndex(i);
Shared::ContinuousFunction::PlotType type = f->plotType();
Poincare::Expression e = f->expressionReduced(context());
if (e.isUndefined() || (
@@ -52,6 +53,17 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
* how fast the function moves... */
float tstep = (tmax-tmin)/10.0938275501223f;
float tCacheMin, tCacheStep;
if (type == ContinuousFunction::PlotType::Cartesian) {
float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin);
tCacheMin = std::isnan(rectLeft) ? tmin : std::max(tmin, rectLeft);
tCacheStep = pixelWidth();
} else {
tCacheMin = tmin;
tCacheStep = tstep;
}
ContinuousFunctionCache::PrepareForCaching(f.operator->(), cch, tCacheMin, tCacheStep);
// Cartesian
if (type == Shared::ContinuousFunction::PlotType::Cartesian) {
drawCartesianCurve(ctx, rect, tmin, tmax, [](float t, void * model, void * context) {
@@ -63,8 +75,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
ContinuousFunction * f = (ContinuousFunction *)model;
Poincare::Context * c = (Poincare::Context *)context;
return f->evaluateXYAtParameter(t, c);
},
&ContinuousFunctionCache::PrepareCache, cch.operator->());
});
/* Draw tangent */
if (m_tangent && record == m_selectedRecord) {
float tangentParameterA = f->approximateDerivative(m_curveViewCursor->x(), context());
@@ -85,9 +96,7 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const {
ContinuousFunction * f = (ContinuousFunction *)model;
Poincare::Context * c = (Poincare::Context *)context;
return f->evaluateXYAtParameter(t, c);
}, f.operator->(), context(), false, f->color(),
true, false, 0.0f, 0.0f, /* drawCurve's default arguments */
&ContinuousFunctionCache::PrepareCache, cch.operator->());
}, f.operator->(), context(), false, f->color());
}
}

View File

@@ -351,18 +351,6 @@ Poincare::Expression ContinuousFunction::sumBetweenBounds(double start, double e
* the derivative table. */
}
Poincare::Coordinate2D<float> ContinuousFunction::checkForCacheHitAndEvaluate(float t, Poincare::Context * context) const {
Poincare::Coordinate2D<float> res(NAN, NAN);
if (cacheIsFilled()) {
res = cache()->valueForParameter(this, t);
}
if (std::isnan(res.x1()) || std::isnan(res.x2())) {
res = privateEvaluateXYAtParameter(t, context);
//res = Poincare::Coordinate2D<float>(privateEvaluateXYAtParameter(t, context).x1(), 0);
}
return res;
}
Ion::Storage::Record::ErrorStatus ContinuousFunction::setContent(const char * c, Poincare::Context * context) {
clearCache();
return ExpressionModelHandle::setContent(c, context);

View File

@@ -26,7 +26,8 @@ public:
static void DefaultName(char buffer[], size_t bufferSize);
static ContinuousFunction NewModel(Ion::Storage::Record::ErrorStatus * error, const char * baseName = nullptr);
ContinuousFunction(Ion::Storage::Record record = Record()) :
Function(record)
Function(record),
m_cache(nullptr)
{}
I18n::Message parameterMessageName() const override;
CodePoint symbol() const override;
@@ -47,8 +48,7 @@ public:
return templatedApproximateAtParameter(t, context);
}
Poincare::Coordinate2D<float> evaluateXYAtParameter(float t, Poincare::Context * context) const override {
//return privateEvaluateXYAtParameter<float>(t, context);
return checkForCacheHitAndEvaluate(t, context);
return (m_cache) ? m_cache->valueForParameter(this, context, t) : privateEvaluateXYAtParameter<float>(t, context);
}
Poincare::Coordinate2D<double> evaluateXYAtParameter(double t, Poincare::Context * context) const override {
return privateEvaluateXYAtParameter<double>(t, context);
@@ -83,14 +83,12 @@ public:
ContinuousFunctionCache * cache() const { return m_cache; }
void setCache(ContinuousFunctionCache * v) { m_cache = v; }
void clearCache() { m_cache = nullptr; }
bool cacheIsFilled() const { return cache() && cache()->filled(); }
Ion::Storage::Record::ErrorStatus setContent(const char * c, Poincare::Context * context) override;
private:
constexpr static float k_polarParamRangeSearchNumberOfPoints = 100.0f; // This is ad hoc, no special justification
typedef Poincare::Coordinate2D<double> (*ComputePointOfInterest)(Poincare::Expression e, char * symbol, double start, double step, double max, Poincare::Context * context);
Poincare::Coordinate2D<double> nextPointOfInterestFrom(double start, double step, double max, Poincare::Context * context, ComputePointOfInterest compute) const;
template <typename T> Poincare::Coordinate2D<T> privateEvaluateXYAtParameter(T t, Poincare::Context * context) const;
Poincare::Coordinate2D<float> checkForCacheHitAndEvaluate(float t, Poincare::Context * context) const;
/* RecordDataBuffer is the layout of the data buffer of Record
* representing a ContinuousFunction. See comment on
* Shared::Function::RecordDataBuffer about packing. */

View File

@@ -1,5 +1,6 @@
#include "continuous_function_cache.h"
#include "continuous_function.h"
#include <limits.h>
namespace Shared {
@@ -8,43 +9,38 @@ constexpr float ContinuousFunctionCache::k_cacheHitTolerance;
constexpr int ContinuousFunctionCache::k_numberOfAvailableCaches;
// public
void ContinuousFunctionCache::PrepareCache(void * f, void * ctx, void * cch, float tMin, float tStep) {
if (!cch) {
void ContinuousFunctionCache::PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep) {
if (!cache) {
/* ContinuousFunctionStore::cacheAtIndex has returned a nullptr : the index
* of the function we are trying to draw is greater than the number of
* available caches, so we do nothing.*/
return;
}
ContinuousFunction * function = (ContinuousFunction *)f;
Poincare::Context * context = (Poincare::Context *)ctx;
if (!function->cache()) {
ContinuousFunctionCache * cache = (ContinuousFunctionCache *)cch;
ContinuousFunction * function = static_cast<ContinuousFunction *>(fun);
if (function->cache() != cache) {
cache->clear();
function->setCache(cache);
}
if (function->cache()->filled() && tStep / StepFactor(function) == function->cache()->step()) {
if (function->plotType() == ContinuousFunction::PlotType::Cartesian) {
function->cache()->pan(function, context, tMin);
}
return;
if (function->plotType() == ContinuousFunction::PlotType::Cartesian && tStep != 0) {
function->cache()->pan(function, tMin);
}
function->cache()->setRange(function, tMin, tStep);
function->cache()->memoize(function, context);
}
void ContinuousFunctionCache::clear() {
m_filled = false;
m_startOfCache = 0;
m_tStep = 0;
invalidateBetween(0, k_sizeOfCache);
}
Poincare::Coordinate2D<float> ContinuousFunctionCache::valueForParameter(const ContinuousFunction * function, float t) const {
int iRes = indexForParameter(function, t);
/* If t does not map to an index, iRes is -1 */
if (iRes < 0) {
return Poincare::Coordinate2D<float>(NAN, NAN);
Poincare::Coordinate2D<float> ContinuousFunctionCache::valueForParameter(const ContinuousFunction * function, Poincare::Context * context, float t) {
int resIndex = indexForParameter(function, t);
if (resIndex < 0) {
return function->privateEvaluateXYAtParameter(t, context);
}
if (function->plotType() == ContinuousFunction::PlotType::Cartesian) {
return Poincare::Coordinate2D<float>(t, m_cache[iRes]);
}
assert(m_startOfCache == 0);
return Poincare::Coordinate2D<float>(m_cache[2*iRes], m_cache[2*iRes+1]);
return valuesAtIndex(function, context, t, resIndex);
}
// private
@@ -55,48 +51,17 @@ float ContinuousFunctionCache::StepFactor(ContinuousFunction * function) {
return (function->plotType() == ContinuousFunction::PlotType::Cartesian) ? 1.f : 16.f;
}
void ContinuousFunctionCache::invalidateBetween(int iInf, int iSup) {
for (int i = iInf; i < iSup; i++) {
m_cache[i] = NAN;
}
}
void ContinuousFunctionCache::setRange(ContinuousFunction * function, float tMin, float tStep) {
m_tMin = tMin;
m_tStep = tStep / StepFactor(function);
}
void ContinuousFunctionCache::memoize(ContinuousFunction * function, Poincare::Context * context) {
m_filled = true;
m_startOfCache = 0;
if (function->plotType() == ContinuousFunction::PlotType::Cartesian) {
memoizeYForX(function, context);
return;
}
memoizeXYForT(function, context);
}
void ContinuousFunctionCache::memoizeYForX(ContinuousFunction * function, Poincare::Context * context) {
memoizeYForXBetweenIndices(function, context, 0, k_sizeOfCache);
}
void ContinuousFunctionCache::memoizeYForXBetweenIndices(ContinuousFunction * function, Poincare::Context * context, int iInf, int iSup) {
assert(function->plotType() == ContinuousFunction::PlotType::Cartesian);
for (int i = iInf; i < iSup; i++) {
m_cache[i] = function->privateEvaluateXYAtParameter(parameterForIndex(i), context).x2();
}
}
void ContinuousFunctionCache::memoizeXYForT(ContinuousFunction * function, Poincare::Context * context) {
assert(function->plotType() != ContinuousFunction::PlotType::Cartesian);
for (int i = 1; i < k_sizeOfCache; i += 2) {
Poincare::Coordinate2D<float> res = function->privateEvaluateXYAtParameter(parameterForIndex(i/2), context);
m_cache[i - 1] = res.x1();
m_cache[i] = res.x2();
}
}
float ContinuousFunctionCache::parameterForIndex(int i) const {
if (i < m_startOfCache) {
i += k_sizeOfCache;
}
return m_tMin + m_tStep * (i - m_startOfCache);
}
int ContinuousFunctionCache::indexForParameter(const ContinuousFunction * function, float t) const {
float delta = (t - m_tMin) / m_tStep;
if (delta < 0 || delta > INT_MAX) {
@@ -104,14 +69,31 @@ int ContinuousFunctionCache::indexForParameter(const ContinuousFunction * functi
}
int res = std::round(delta);
assert(res >= 0);
if (res >= k_sizeOfCache || std::abs(res - delta) > k_cacheHitTolerance) {
if ((res >= k_sizeOfCache && function->plotType() == ContinuousFunction::PlotType::Cartesian)
|| (res >= k_sizeOfCache / 2 && function->plotType() != ContinuousFunction::PlotType::Cartesian)
|| std::abs(res - delta) > k_cacheHitTolerance) {
return -1;
}
assert(function->plotType() == ContinuousFunction::PlotType::Cartesian || m_startOfCache == 0);
return (res + m_startOfCache) % k_sizeOfCache;
}
void ContinuousFunctionCache::pan(ContinuousFunction * function, Poincare::Context * context, float newTMin) {
Poincare::Coordinate2D<float> ContinuousFunctionCache::valuesAtIndex(const ContinuousFunction * function, Poincare::Context * context, float t, int i) {
if (function->plotType() == ContinuousFunction::PlotType::Cartesian) {
if (std::isnan(m_cache[i])) {
m_cache[i] = function->privateEvaluateXYAtParameter(t, context).x2();
}
return Poincare::Coordinate2D<float>(t, m_cache[i]);
}
if (std::isnan(m_cache[2 * i]) || std::isnan(m_cache[2 * i + 1])) {
Poincare::Coordinate2D<float> res = function->privateEvaluateXYAtParameter(t, context);
m_cache[2 * i] = res.x1();
m_cache[2 * i + 1] = res.x2();
}
return Poincare::Coordinate2D<float>(m_cache[2 * i], m_cache[2 * i + 1]);
}
void ContinuousFunctionCache::pan(ContinuousFunction * function, float newTMin) {
assert(function->plotType() == ContinuousFunction::PlotType::Cartesian);
if (newTMin == m_tMin) {
return;
@@ -120,12 +102,12 @@ void ContinuousFunctionCache::pan(ContinuousFunction * function, Poincare::Conte
float dT = (newTMin - m_tMin) / m_tStep;
m_tMin = newTMin;
if (std::abs(dT) > INT_MAX) {
memoize(function, context);
clear();
return;
}
int dI = std::round(dT);
if (dI >= k_sizeOfCache || dI <= -k_sizeOfCache || std::abs(dT - dI) > k_cacheHitTolerance) {
memoize(function, context);
clear();
return;
}
@@ -136,17 +118,17 @@ void ContinuousFunctionCache::pan(ContinuousFunction * function, Poincare::Conte
}
if (dI > 0) {
if (m_startOfCache > oldStart) {
memoizeYForXBetweenIndices(function, context, oldStart, m_startOfCache);
invalidateBetween(oldStart, m_startOfCache);
} else {
memoizeYForXBetweenIndices(function, context, oldStart, k_sizeOfCache);
memoizeYForXBetweenIndices(function, context, 0, m_startOfCache);
invalidateBetween(oldStart, k_sizeOfCache);
invalidateBetween(0, m_startOfCache);
}
} else {
if (m_startOfCache > oldStart) {
memoizeYForXBetweenIndices(function, context, m_startOfCache, k_sizeOfCache);
memoizeYForXBetweenIndices(function, context, 0, oldStart);
invalidateBetween(m_startOfCache, k_sizeOfCache);
invalidateBetween(0, oldStart);
} else {
memoizeYForXBetweenIndices(function, context, m_startOfCache, oldStart);
invalidateBetween(m_startOfCache, oldStart);
}
}
}

View File

@@ -15,12 +15,13 @@ public:
* function */
static constexpr int k_numberOfAvailableCaches = 2;
static void PrepareCache(void * f, void * ctx, void * cch, float tMin, float tStep);
static void PrepareForCaching(void * fun, ContinuousFunctionCache * cache, float tMin, float tStep);
ContinuousFunctionCache() { clear(); }
float step() const { return m_tStep; }
bool filled() const { return m_filled; }
void clear();
Poincare::Coordinate2D<float> valueForParameter(const ContinuousFunction * function, float t) const;
Poincare::Coordinate2D<float> valueForParameter(const ContinuousFunction * function, Poincare::Context * context, float t);
private:
/* The size of the cache is chosen to optimize the display of cartesian
* function */
@@ -29,14 +30,11 @@ private:
static float StepFactor(ContinuousFunction * function);
void invalidateBetween(int iInf, int iSup);
void setRange(ContinuousFunction * function, float tMin, float tStep);
void memoize(ContinuousFunction * function, Poincare::Context * context);
void memoizeYForX(ContinuousFunction * function, Poincare::Context * context);
void memoizeYForXBetweenIndices(ContinuousFunction * function, Poincare::Context * context, int iInf, int iSup);
void memoizeXYForT(ContinuousFunction * function, Poincare::Context * context);
float parameterForIndex(int i) const;
int indexForParameter(const ContinuousFunction * function, float t) const;
void pan(ContinuousFunction * function, Poincare::Context * context, float newTMin);
Poincare::Coordinate2D<float> valuesAtIndex(const ContinuousFunction * function, Poincare::Context * context, float t, int i);
void pan(ContinuousFunction * function, float newTMin);
float m_tMin, m_tStep;
float m_cache[k_sizeOfCache];
@@ -44,7 +42,6 @@ private:
* with cartesian functions. When dealing with parametric or polar functions,
* m_startOfCache should be zero.*/
int m_startOfCache;
bool m_filled;
};
}

View File

@@ -602,8 +602,7 @@ const uint8_t thickStampMask[(thickStampSize+1)*(thickStampSize+1)] = {
constexpr static int k_maxNumberOfIterations = 10;
void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation, PrepareContinuousFunction functionPreparator, void * cache) const {
functionPreparator(model, context, cache, tStart, tStep);
void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation) const {
float previousT = NAN;
float t = NAN;
float previousX = NAN;
@@ -635,7 +634,7 @@ void CurveView::drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd
} while (true);
}
void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation, PrepareContinuousFunction functionPreparator, void * cache) const {
void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick, bool colorUnderCurve, float colorLowerBound, float colorUpperBound, EvaluateXYForDoubleParameter xyDoubleEvaluation) const {
float rectLeft = pixelToFloat(Axis::Horizontal, rect.left() - k_externRectMargin);
float rectRight = pixelToFloat(Axis::Horizontal, rect.right() + k_externRectMargin);
float tStart = std::isnan(rectLeft) ? xMin : std::max(xMin, rectLeft);
@@ -645,7 +644,7 @@ void CurveView::drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, flo
return;
}
float tStep = pixelWidth();
drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation, functionPreparator, cache);
drawCurve(ctx, rect, tStart, tEnd, tStep, xyFloatEvaluation, model, context, true, color, thick, colorUnderCurve, colorLowerBound, colorUpperBound, xyDoubleEvaluation);
}
void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth,

View File

@@ -19,7 +19,6 @@ public:
typedef Poincare::Coordinate2D<float> (*EvaluateXYForFloatParameter)(float t, void * model, void * context);
typedef Poincare::Coordinate2D<double> (*EvaluateXYForDoubleParameter)(double t, void * model, void * context);
typedef float (*EvaluateYForX)(float x, void * model, void * context);
typedef void (* PrepareContinuousFunction)(void * model, void * context, void * cache, float xMin, float xStep);
enum class Axis {
Horizontal = 0,
Vertical = 1
@@ -108,8 +107,8 @@ protected:
void drawGrid(KDContext * ctx, KDRect rect) const;
void drawAxes(KDContext * ctx, KDRect rect) const;
void drawAxis(KDContext * ctx, KDRect rect, Axis axis) const;
void drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr,PrepareContinuousFunction functionPreparator = [](void * model, void * context, void * cache, float xMin, float xStep) {}, void * cache = nullptr) const;
void drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr, PrepareContinuousFunction functionPreparator = [](void * model, void * context, void * cache, float xMin, float xStep) {}, void * cache = nullptr) const;
void drawCurve(KDContext * ctx, KDRect rect, float tStart, float tEnd, float tStep, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, bool drawStraightLinesEarly, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr) const;
void drawCartesianCurve(KDContext * ctx, KDRect rect, float xMin, float xMax, EvaluateXYForFloatParameter xyFloatEvaluation, void * model, void * context, KDColor color, bool thick = true, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, EvaluateXYForDoubleParameter xyDoubleEvaluation = nullptr) const;
void drawHistogram(KDContext * ctx, KDRect rect, EvaluateYForX yEvaluation, void * model, void * context, float firstBarAbscissa, float barWidth,
bool fillBar, KDColor defaultColor, KDColor highlightColor, float highlightLowerBound = INFINITY, float highlightUpperBound = -INFINITY) const;
void computeLabels(Axis axis);