diff --git a/apps/calculation/app.cpp b/apps/calculation/app.cpp index 5cb19d37d..7dd1646e9 100644 --- a/apps/calculation/app.cpp +++ b/apps/calculation/app.cpp @@ -35,7 +35,6 @@ App::Descriptor * App::Snapshot::descriptor() { } void App::Snapshot::tidy() { - m_calculationStore.tidy(); } App::App(Snapshot * snapshot) : diff --git a/apps/calculation/calculation.cpp b/apps/calculation/calculation.cpp index 98c97c1fa..d298ce4f3 100644 --- a/apps/calculation/calculation.cpp +++ b/apps/calculation/calculation.cpp @@ -1,7 +1,5 @@ #include "calculation.h" -#include "calculation_store.h" #include "../shared/poincare_helpers.h" -#include #include #include #include @@ -14,50 +12,70 @@ namespace Calculation { static inline KDCoordinate maxCoordinate(KDCoordinate x, KDCoordinate y) { return x > y ? x : y; } -Calculation::Calculation() : - m_inputText(), - m_exactOutputText(), - m_approximateOutputText(), - m_displayOutput(DisplayOutput::Unknown), - m_height(-1), - m_expandedHeight(-1), - m_equalSign(EqualSign::Unknown) -{ -} - bool Calculation::operator==(const Calculation& c) { - return strcmp(m_inputText, c.m_inputText) == 0 - && strcmp(m_approximateOutputText, c.m_approximateOutputText) == 0 + return strcmp(inputText(), c.inputText()) == 0 + && strcmp(approximateOutputText(), c.approximateOutputText()) == 0 /* Some calculations can make appear trigonometric functions in their * exact output. Their argument will be different with the angle unit * preferences but both input and approximate output will be the same. * For example, i^(sqrt(3)) = cos(sqrt(3)*pi/2)+i*sin(sqrt(3)*pi/2) if * angle unit is radian and i^(sqrt(3)) = cos(sqrt(3)*90+i*sin(sqrt(3)*90) * in degree. */ - && strcmp(m_exactOutputText, c.m_exactOutputText) == 0; + && strcmp(exactOutputText(), c.exactOutputText()) == 0; } -void Calculation::reset() { - m_inputText[0] = 0; - m_exactOutputText[0] = 0; - m_approximateOutputText[0] = 0; - tidy(); -} - -void Calculation::setContent(const char * c, Context * context, Expression ansExpression) { - reset(); - { - Symbol ansSymbol = Symbol::Ans(); - Expression input = Expression::Parse(c).replaceSymbolWithExpression(ansSymbol, ansExpression); - /* We do not store directly the text enter by the user because we do not want - * to keep Ans symbol in the calculation store. */ - PoincareHelpers::Serialize(input, m_inputText, sizeof(m_inputText)); +Calculation * Calculation::next() const { + const char * result = reinterpret_cast(this) + sizeof(Calculation); + for (int i = 0; i < 3; i++) { + result = result + strlen(result) + 1; // Pass inputText, exactOutputText, ApproximateOutputText } - Expression exactOutput; - Expression approximateOutput; - PoincareHelpers::ParseAndSimplifyAndApproximate(m_inputText, &exactOutput, &approximateOutput, context, false); - PoincareHelpers::Serialize(exactOutput, m_exactOutputText, sizeof(m_exactOutputText)); - PoincareHelpers::Serialize(approximateOutput, m_approximateOutputText, sizeof(m_approximateOutputText)); + return reinterpret_cast(const_cast(result)); +} + +const char * Calculation::approximateOutputText() const { + const char * exactOutput = exactOutputText(); + return exactOutput + strlen(exactOutput) + 1; +} + +Expression Calculation::input() { + return Expression::Parse(m_inputText); +} + +Expression Calculation::exactOutput() { + /* Because the angle unit might have changed, we do not simplify again. We + * thereby avoid turning cos(Pi/4) into sqrt(2)/2 and displaying + * 'sqrt(2)/2 = 0.999906' (which is totally wrong) instead of + * 'cos(pi/4) = 0.999906' (which is true in degree). */ + Expression exactOutput = Expression::Parse(exactOutputText()); + if (exactOutput.isUninitialized()) { + return Undefined::Builder(); + } + return exactOutput; +} + +Expression Calculation::approximateOutput(Context * context) { + /* To ensure that the expression 'm_output' is a matrix or a complex, we + * call 'evaluate'. */ + Expression exp = Expression::Parse(approximateOutputText()); + if (exp.isUninitialized()) { + /* TODO LEA replace with assert + * exp might be uninitialized because the serialization did not fit in + * the buffer. Put a special error instead of "undef". */ + return Undefined::Builder(); + } + return PoincareHelpers::Approximate(exp, context); +} + +Layout Calculation::createInputLayout() { + return input().createLayout(Preferences::PrintFloatMode::Decimal, PrintFloat::k_numberOfStoredSignificantDigits); +} + +Layout Calculation::createExactOutputLayout() { + return PoincareHelpers::CreateLayout(exactOutput()); +} + +Layout Calculation::createApproximateOutputLayout(Context * context) { + return PoincareHelpers::CreateLayout(approximateOutput(context)); } KDCoordinate Calculation::height(Context * context, bool expanded) { @@ -92,80 +110,6 @@ KDCoordinate Calculation::height(Context * context, bool expanded) { return *memoizedHeight; } -const char * Calculation::inputText() { - return m_inputText; -} - -const char * Calculation::exactOutputText() { - return m_exactOutputText; -} - -const char * Calculation::approximateOutputText() { - return m_approximateOutputText; -} - -Expression Calculation::input() { - return Expression::Parse(m_inputText); -} - -Layout Calculation::createInputLayout() { - return input().createLayout(Preferences::PrintFloatMode::Decimal, PrintFloat::k_numberOfStoredSignificantDigits); -} - -bool Calculation::isEmpty() { - /* To test if a calculation is empty, we need to test either m_inputText or - * m_exactOutputText or m_approximateOutputText, the only three fields that - * are not lazy-loaded. We choose m_exactOutputText to consider that a - * calculation being added is still empty until the end of the method - * 'setContent'. Indeed, during 'setContent' method, 'ans' evaluation calls - * the evaluation of the last calculation only if the calculation being - * filled is not taken into account.*/ - if (strlen(m_approximateOutputText) == 0) { - return true; - } - return false; -} - -void Calculation::tidy() { - /* Uninitialized all Expression stored to free the Pool */ - m_displayOutput = DisplayOutput::Unknown; - m_height = -1; - m_expandedHeight = -1; - m_equalSign = EqualSign::Unknown; -} - -Expression Calculation::exactOutput() { - /* Because the angle unit might have changed, we do not simplify again. We - * thereby avoid turning cos(Pi/4) into sqrt(2)/2 and displaying - * 'sqrt(2)/2 = 0.999906' (which is totally wrong) instead of - * 'cos(pi/4) = 0.999906' (which is true in degree). */ - Expression exactOutput = Expression::Parse(m_exactOutputText); - if (exactOutput.isUninitialized()) { - return Undefined::Builder(); - } - return exactOutput; -} - -Layout Calculation::createExactOutputLayout() { - return PoincareHelpers::CreateLayout(exactOutput()); -} - -Expression Calculation::approximateOutput(Context * context) { - /* To ensure that the expression 'm_output' is a matrix or a complex, we - * call 'evaluate'. */ - Expression exp = Expression::Parse(m_approximateOutputText); - if (exp.isUninitialized()) { - /* TODO: exp might be uninitialized because the serialization did not fit in - * the buffer. Put a special error instead of "undef". */ - return Undefined::Builder(); - } - return PoincareHelpers::Approximate(exp, context); -} - -Layout Calculation::createApproximateOutputLayout(Context * context) { - return PoincareHelpers::CreateLayout(approximateOutput(context)); -} - Calculation::DisplayOutput Calculation::displayOutput(Context * context) { if (m_displayOutput != DisplayOutput::Unknown) { return m_displayOutput; @@ -185,15 +129,20 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { context, true)) { m_displayOutput = DisplayOutput::ApproximateOnly; - } else if (strcmp(m_exactOutputText, m_approximateOutputText) == 0) { + } else if (strcmp(exactOutputText(), approximateOutputText()) == 0) { /* If the exact and approximate results' texts are equal and their layouts * too, do not display the exact result. If the two layouts are not equal * because of the number of significant digits, we display both. */ m_displayOutput = exactAndApproximateDisplayedOutputsAreEqual(context) == Calculation::EqualSign::Equal ? DisplayOutput::ApproximateOnly : DisplayOutput::ExactAndApproximate; - } else if (strcmp(m_exactOutputText, Undefined::Name()) == 0 || strcmp(m_approximateOutputText, Unreal::Name()) == 0 || exactOutput().type() == ExpressionNode::Type::Undefined) { + } else if (strcmp(exactOutputText(), Undefined::Name()) == 0 + || strcmp(approximateOutputText(), Unreal::Name()) == 0 + || exactOutput().type() == ExpressionNode::Type::Undefined) + { // If the approximate result is 'unreal' or the exact result is 'undef' m_displayOutput = DisplayOutput::ApproximateOnly; - } else if (input().recursivelyMatches(Expression::IsApproximate, context) || exactOutput().recursivelyMatches(Expression::IsApproximate, context)) { + } else if (input().recursivelyMatches(Expression::IsApproximate, context) + || exactOutput().recursivelyMatches(Expression::IsApproximate, context)) + { m_displayOutput = DisplayOutput::ExactAndApproximateToggle; } else { m_displayOutput = DisplayOutput::ExactAndApproximate; @@ -204,8 +153,9 @@ Calculation::DisplayOutput Calculation::displayOutput(Context * context) { bool Calculation::shouldOnlyDisplayExactOutput() { /* If the input is a "store in a function", do not display the approximate * result. This prevents x->f(x) from displaying x = undef. */ - return input().type() == ExpressionNode::Type::Store - && input().childAtIndex(1).type() == ExpressionNode::Type::Function; + Expression i = input(); + return i.type() == ExpressionNode::Type::Store + && i.childAtIndex(1).type() == ExpressionNode::Type::Function; } Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context) { @@ -215,7 +165,7 @@ Calculation::EqualSign Calculation::exactAndApproximateDisplayedOutputsAreEqual( constexpr int bufferSize = Constant::MaxSerializedExpressionSize; char buffer[bufferSize]; Preferences * preferences = Preferences::sharedPreferences(); - Expression exactOutputExpression = PoincareHelpers::ParseAndSimplify(m_exactOutputText, context, false); + Expression exactOutputExpression = PoincareHelpers::ParseAndSimplify(exactOutputText(), context, false); if (exactOutputExpression.isUninitialized()) { exactOutputExpression = Undefined::Builder(); } diff --git a/apps/calculation/calculation.h b/apps/calculation/calculation.h index ff00be74c..c9d23c9f1 100644 --- a/apps/calculation/calculation.h +++ b/apps/calculation/calculation.h @@ -10,6 +10,13 @@ namespace Calculation { class CalculationStore; + +/* A calculation is: + * | uint8_t |KDCoordinate| KDCoordinate | uint8_t | ... | ... | ... | + * |m_displayOutput| m_height |m_expandedHeight|m_equalSign|m_inputText|m_exactOuputText|m_approximateOuputText| + * + * */ +#pragma pack(push,1) class Calculation { public: enum class EqualSign : uint8_t { @@ -26,23 +33,39 @@ public: ExactAndApproximateToggle }; - Calculation(); + /* It is not really the minimal size, but it clears enough space for most + * calculations instead of clearing less space, then fail to serialize, clear + * more space, fail to serialize, clear more space, etc., until reaching + * sufficient free space. */ + static int MinimalSize() { return sizeof(uint8_t) + 2*sizeof(KDCoordinate) + sizeof(uint8_t) + 3*Constant::MaxSerializedExpressionSize; } + + Calculation() : + m_displayOutput(DisplayOutput::Unknown), + m_height(-1), + m_expandedHeight(-1), + m_equalSign(EqualSign::Unknown) + { + assert(sizeof(m_inputText) == 0); + } bool operator==(const Calculation& c); - /* c.reset() is the equivalent of c = Calculation() without copy assingment. */ - void reset(); - void setContent(const char * c, Poincare::Context * context, Poincare::Expression ansExpression); - KDCoordinate height(Poincare::Context * context, bool expanded = false); - const char * inputText(); - const char * exactOutputText(); - const char * approximateOutputText(); + Calculation * next() const; + + // Texts + const char * inputText() const { return m_inputText; } + const char * exactOutputText() const { return m_inputText + strlen(m_inputText) + 1; } + const char * approximateOutputText() const; + + // Expressions Poincare::Expression input(); - Poincare::Layout createInputLayout(); - Poincare::Expression approximateOutput(Poincare::Context * context); Poincare::Expression exactOutput(); + Poincare::Expression approximateOutput(Poincare::Context * context); + + // Layouts + Poincare::Layout createInputLayout(); Poincare::Layout createExactOutputLayout(); Poincare::Layout createApproximateOutputLayout(Poincare::Context * context); - bool isEmpty(); - void tidy(); + + KDCoordinate height(Poincare::Context * context, bool expanded = false); DisplayOutput displayOutput(Poincare::Context * context); bool shouldOnlyDisplayExactOutput(); EqualSign exactAndApproximateDisplayedOutputsAreEqual(Poincare::Context * context); @@ -51,14 +74,13 @@ private: /* Buffers holding text expressions have to be longer than the text written * by user (of maximum length TextField::maxBufferSize()) because when we * print an expression we add omitted signs (multiplications, parenthesis...) */ - char m_inputText[Constant::MaxSerializedExpressionSize]; - char m_exactOutputText[Constant::MaxSerializedExpressionSize]; - char m_approximateOutputText[Constant::MaxSerializedExpressionSize]; DisplayOutput m_displayOutput; KDCoordinate m_height; KDCoordinate m_expandedHeight; EqualSign m_equalSign; + char m_inputText[0]; // MUST be the last member variable }; +#pragma pack(pop) } diff --git a/apps/calculation/calculation_store.cpp b/apps/calculation/calculation_store.cpp index ed7e31600..0ae66f9d2 100644 --- a/apps/calculation/calculation_store.cpp +++ b/apps/calculation/calculation_store.cpp @@ -1,108 +1,168 @@ #include "calculation_store.h" -#include +#include "../shared/poincare_helpers.h" #include #include +#include + using namespace Poincare; +using namespace Shared; namespace Calculation { -Calculation * CalculationStore::push(const char * text, Context * context) { - Calculation * result = &m_calculations[m_startIndex]; - result->setContent(text, context, ansExpression(context)); - m_startIndex++; - if (m_startIndex >= k_maxNumberOfCalculations) { - m_startIndex = 0; +ExpiringPointer CalculationStore::calculationAtIndex(int i) { + assert(!m_slidedBuffer); + assert(i >= 0 && i < m_numberOfCalculations); + int currentIndex = 0; + for (Calculation * c : *this) { + if (currentIndex == i) { + return ExpiringPointer(c); + } + currentIndex++; } - return result; + assert(false); + return nullptr; } -Calculation * CalculationStore::calculationAtIndex(int i) { - int j = 0; - Calculation * currentCalc = &m_calculations[m_startIndex]; - Calculation * previousCalc = nullptr; - while (j <= i) { - if (!currentCalc++->isEmpty()) { - previousCalc = currentCalc - 1; - j++; - } - if (currentCalc >= m_calculations + k_maxNumberOfCalculations) { - currentCalc = m_calculations; - } - } - return previousCalc; -} +ExpiringPointer CalculationStore::push(const char * text, Context * context) { + /* Compute ans now, before the buffer is slided and before the calculation + * might be deleted */ + Expression ans = ansExpression(context); // TODO LEA compute ans only if Ans is in the input ? -int CalculationStore::numberOfCalculations() { - Calculation * currentCalc= m_calculations; - int numberOfCalculations = 0; - while (currentCalc < m_calculations + k_maxNumberOfCalculations) { - if (!currentCalc++->isEmpty()) { - numberOfCalculations++; - } + // Prepare the buffer for the new calculation + int minSize = Calculation::MinimalSize(); + assert(k_bufferSize > minSize); + while (remainingBufferSize() < minSize) { + deleteLastCalculation(); } - return numberOfCalculations; + char * newCalculationsLocation = slideCalculationsToEndOfBuffer(); + char * nextSerializationLocation = m_buffer; + // Add the beginning of the calculation + { + /* Copy the begining of the calculation. The calculation minimal size is + * available, so this memcpy will not overide anything. */ + Calculation newCalc = Calculation(); + size_t calcSize = sizeof(newCalc); + memcpy(nextSerializationLocation, &newCalc, calcSize); + nextSerializationLocation += calcSize; + } + /* Add the input expression. + * We do not store directly the text entered by the user because we do not + * want to keep Ans symbol in the calculation store. */ + Expression input = Expression::Parse(text).replaceSymbolWithExpression(Symbol::Ans(), ans); + const char * inputSerialization = nextSerializationLocation; + serializeExpression(input, nextSerializationLocation, &newCalculationsLocation); + nextSerializationLocation += strlen(nextSerializationLocation) + 1; + Expression exactOutput; + Expression approximateOutput; + PoincareHelpers::ParseAndSimplifyAndApproximate(inputSerialization, &exactOutput, &approximateOutput, context, false); + serializeExpression(exactOutput, nextSerializationLocation, &newCalculationsLocation); + nextSerializationLocation += strlen(nextSerializationLocation) + 1; + serializeExpression(approximateOutput, nextSerializationLocation, &newCalculationsLocation); + nextSerializationLocation += strlen(nextSerializationLocation) + 1; + size_t slideSize = m_buffer + k_bufferSize - newCalculationsLocation; + memcpy(nextSerializationLocation, newCalculationsLocation, slideSize); + m_slidedBuffer = false; + m_numberOfCalculations++; + m_bufferEnd+= nextSerializationLocation - m_buffer; + return ExpiringPointer(reinterpret_cast(m_buffer)); } void CalculationStore::deleteCalculationAtIndex(int i) { - int numberOfCalc = numberOfCalculations(); - assert(i >= 0 && i < numberOfCalc); - int indexFirstCalc = m_startIndex; - while (m_calculations[indexFirstCalc].isEmpty()) { - indexFirstCalc++; - if (indexFirstCalc == k_maxNumberOfCalculations) { - indexFirstCalc = 0; - } - assert(indexFirstCalc != m_startIndex); - } - int absoluteIndexCalculationI = indexFirstCalc+i; - absoluteIndexCalculationI = absoluteIndexCalculationI >= k_maxNumberOfCalculations ? absoluteIndexCalculationI - k_maxNumberOfCalculations : absoluteIndexCalculationI; - - int index = absoluteIndexCalculationI; - for (int k = i; k < numberOfCalc-1; k++) { - int nextIndex = index+1 >= k_maxNumberOfCalculations ? 0 : index+1; - m_calculations[index] = m_calculations[nextIndex]; - index++; - if (index == k_maxNumberOfCalculations) { - index = 0; - } - } - m_calculations[index].reset(); - m_startIndex--; - if (m_startIndex == -1) { - m_startIndex = k_maxNumberOfCalculations-1; - } + assert(i >= 0 && i < m_numberOfCalculations); + assert(!m_slidedBuffer); + ExpiringPointer calcI = calculationAtIndex(i); + char * nextCalc = reinterpret_cast(calcI->next()); + assert(m_bufferEnd >= nextCalc); + size_t slidingSize = m_bufferEnd - nextCalc; + memcpy((char *)(calcI.pointer()), nextCalc, slidingSize); + m_bufferEnd -= (nextCalc - (char *)(calcI.pointer())); + m_numberOfCalculations--; } void CalculationStore::deleteAll() { - m_startIndex = 0; - for (int i = 0; i < k_maxNumberOfCalculations; i++) { - m_calculations[i].reset(); - } -} - -void CalculationStore::tidy() { - for (int i = 0; i < k_maxNumberOfCalculations; i++) { - m_calculations[i].tidy(); - } + assert(!m_slidedBuffer); + m_bufferEnd = m_buffer; + m_numberOfCalculations = 0; } Expression CalculationStore::ansExpression(Context * context) { if (numberOfCalculations() == 0) { return Rational::Builder(0); } - Calculation * lastCalculation = calculationAtIndex(numberOfCalculations()-1); + ExpiringPointer mostRecentCalculation = calculationAtIndex(0); /* Special case: the exact output is a Store/Equal expression. * Store/Equal expression can only be at the root of an expression. * To avoid turning 'ans->A' in '2->A->A' or '2=A->A' (which cannot be * parsed), ans is replaced by the approximation output when any Store or * Equal expression appears. */ - bool exactOuptutInvolvesStoreEqual = lastCalculation->exactOutput().recursivelyMatches([](const Expression e, Context * context) { + bool exactOuptutInvolvesStoreEqual = mostRecentCalculation->exactOutput().recursivelyMatches([](const Expression e, Context * context) { return e.type() == ExpressionNode::Type::Store || e.type() == ExpressionNode::Type::Equal; }, context, false); - if (lastCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOuptutInvolvesStoreEqual) { - return lastCalculation->approximateOutput(context); + if (mostRecentCalculation->input().recursivelyMatches(Expression::IsApproximate, context) || exactOuptutInvolvesStoreEqual) { + return mostRecentCalculation->approximateOutput(context); + } + return mostRecentCalculation->exactOutput(); +} + +void CalculationStore::serializeExpression(Expression e, char * location, char * * newCalculationsLocation) { + assert(m_slidedBuffer); + pushExpression( + [](char * location, size_t locationSize, void * e) { + return PoincareHelpers::Serialize(*(Expression *)e, location, locationSize) < locationSize-1; //TODO LEA check the return value + }, + &e, location, newCalculationsLocation); +} + +char * CalculationStore::slideCalculationsToEndOfBuffer() { + int calculationsSize = m_bufferEnd - m_buffer; + char * calculationsNewPosition = m_buffer + k_bufferSize - calculationsSize; + memcpy(calculationsNewPosition, m_buffer, calculationsSize); + m_slidedBuffer = true; + return calculationsNewPosition; +} + +size_t CalculationStore::deleteLastCalculation(const char * calculationsStart) { + assert(m_numberOfCalculations > 0); + size_t result; + if (!m_slidedBuffer) { + assert(calculationsStart == nullptr); + const char * previousBufferEnd = m_bufferEnd; + m_bufferEnd = lastCalculationPosition(m_buffer); + assert(previousBufferEnd > m_bufferEnd); + result = previousBufferEnd - m_bufferEnd; + } else { + assert(calculationsStart != nullptr); + const char * lastCalc = lastCalculationPosition(calculationsStart); + assert(*lastCalc == 0); + result = m_buffer + k_bufferSize - lastCalc; + memcpy(const_cast(calculationsStart + result), calculationsStart, m_buffer + k_bufferSize - calculationsStart - result); + } + m_numberOfCalculations--; + return result; +} + +const char * CalculationStore::lastCalculationPosition(const char * calculationsStart) const { + // TODO LEA: Make this faster? + assert(calculationsStart >= m_buffer && calculationsStart < m_buffer + k_bufferSize); + Calculation * c = reinterpret_cast(const_cast(calculationsStart)); + int calculationIndex = 0; + while (calculationIndex < m_numberOfCalculations - 1) { + c = c->next(); + } + return reinterpret_cast(c); +} + +void CalculationStore::pushExpression(ValueCreator valueCreator, Expression * expression, char * location, char * * newCalculationsLocation) { + while (!valueCreator(location, *newCalculationsLocation - location, expression) + && *newCalculationsLocation < m_buffer + k_bufferSize) + { + *newCalculationsLocation = *newCalculationsLocation + deleteLastCalculation(); + assert(*newCalculationsLocation <= m_buffer + k_bufferSize); + } + if (*newCalculationsLocation >= m_buffer + k_bufferSize) { + //TODO LEA the expression does not fit in the buffer even empty + // Push undef if calculation is too big !!! (and push undef before too if needed!!!) } - return lastCalculation->exactOutput(); } } diff --git a/apps/calculation/calculation_store.h b/apps/calculation/calculation_store.h index 9eeb3bd86..1fdbcef71 100644 --- a/apps/calculation/calculation_store.h +++ b/apps/calculation/calculation_store.h @@ -2,23 +2,66 @@ #define CALCULATION_CALCULATION_STORE_H #include "calculation.h" +#include namespace Calculation { +/* To optimize the storage space, we use one big buffer for all calculations. + * + * The previous solution was to keep 10 calculations, each containing 3 buffers + * (for input and outputs). To optimize the storage, we then wanted to put all + * outputs in a cache where they could be deleted to add a new entry, and + * recomputed on cache miss. However, the computation depends too much on the + * state of the memory for this to be possible. For instance: + * 6->a + * a+1 + * Perform some big computations that remove a+1 from the cache + * Delete a from the variable box. + * Scroll up to display a+1 : a does not exist anymore so the outputs won't be + * recomputed correctly. + * + * Now we do not cap the number of calculations and just delete the oldests to + * create space for a new calculation. */ + class CalculationStore { public: - CalculationStore() : m_startIndex(0) {} - Calculation * calculationAtIndex(int i); - Calculation * push(const char * text, Poincare::Context * context); + CalculationStore() : m_bufferEnd(m_buffer), m_numberOfCalculations(0), m_slidedBuffer(false) {} + Shared::ExpiringPointer calculationAtIndex(int i); + Shared::ExpiringPointer push(const char * text, Poincare::Context * context); void deleteCalculationAtIndex(int i); void deleteAll(); - int numberOfCalculations(); - void tidy(); + int numberOfCalculations() const { return m_numberOfCalculations; } Poincare::Expression ansExpression(Poincare::Context * context); - static constexpr int k_maxNumberOfCalculations = 10; private: - int m_startIndex; - Calculation m_calculations[k_maxNumberOfCalculations]; + static constexpr int k_bufferSize = 10 * 3 * Constant::MaxSerializedExpressionSize; + + class CalculationIterator { + public: + CalculationIterator(const char * c) : m_calculation(reinterpret_cast(const_cast(c))) {} + Calculation * operator*() { return m_calculation; } + bool operator!=(const CalculationIterator& it) const { return (m_calculation != it.m_calculation); } + CalculationIterator & operator++() { + m_calculation = m_calculation->next(); + return *this; + } + protected: + Calculation * m_calculation; + }; + + CalculationIterator begin() const { return CalculationIterator(m_buffer); } + CalculationIterator end() const { return CalculationIterator(m_bufferEnd); } + + int remainingBufferSize() const { assert(m_bufferEnd >= m_buffer); return k_bufferSize - (m_bufferEnd - m_buffer); } + void serializeExpression(Poincare::Expression e, char * location, char * * newCalculationsLocation); + char * slideCalculationsToEndOfBuffer(); // returns the new position of the calculations + size_t deleteLastCalculation(const char * calculationsStart = nullptr); + const char * lastCalculationPosition(const char * calculationsStart) const; + typedef bool (*ValueCreator)(char * location, size_t locationSize, void * e); + void pushExpression(ValueCreator valueCrator, Poincare::Expression * expression, char * location, char * * newCalculationsLocation); + char m_buffer[k_bufferSize]; + const char * m_bufferEnd; + int m_numberOfCalculations; + bool m_slidedBuffer; }; } diff --git a/apps/calculation/history_controller.cpp b/apps/calculation/history_controller.cpp index 6a17e9226..c7f79f767 100644 --- a/apps/calculation/history_controller.cpp +++ b/apps/calculation/history_controller.cpp @@ -49,7 +49,7 @@ bool HistoryController::handleEvent(Ion::Events::Event event) { EditExpressionController * editController = (EditExpressionController *)parentResponder(); m_selectableTableView.deselectTable(); Container::activeApp()->setFirstResponder(editController); - Calculation * calculation = m_calculationStore->calculationAtIndex(focusRow); + Shared::ExpiringPointer calculation = m_calculationStore->calculationAtIndex(focusRow); if (subviewType == SubviewType::Input) { editController->insertTextBody(calculation->inputText()); } else { @@ -141,7 +141,7 @@ int HistoryController::reusableCellCount(int type) { void HistoryController::willDisplayCellForIndex(HighlightCell * cell, int index) { HistoryViewCell * myCell = (HistoryViewCell *)cell; - myCell->setCalculation(m_calculationStore->calculationAtIndex(index), index == selectedRow() && selectedSubviewType() == SubviewType::Output); + myCell->setCalculation((m_calculationStore->calculationAtIndex(index)).pointer(), index == selectedRow() && selectedSubviewType() == SubviewType::Output); myCell->setEven(index%2 == 0); myCell->setHighlighted(myCell->isHighlighted()); } @@ -150,7 +150,7 @@ KDCoordinate HistoryController::rowHeight(int j) { if (j >= m_calculationStore->numberOfCalculations()) { return 0; } - Calculation * calculation = m_calculationStore->calculationAtIndex(j); + Shared::ExpiringPointer calculation = m_calculationStore->calculationAtIndex(j); return calculation->height(App::app()->localContext(), j == selectedRow() && selectedSubviewType() == SubviewType::Output) + 4 * Metric::CommonSmallMargin; } diff --git a/apps/calculation/test/calculation_store.cpp b/apps/calculation/test/calculation_store.cpp index 807df942c..fd253853a 100644 --- a/apps/calculation/test/calculation_store.cpp +++ b/apps/calculation/test/calculation_store.cpp @@ -8,46 +8,32 @@ using namespace Poincare; using namespace Calculation; -void assert_store_is(CalculationStore * store, const char * result[10]) { +void assert_store_is(CalculationStore * store, const char * * result) { for (int i = 0; i < store->numberOfCalculations(); i++) { quiz_assert(strcmp(store->calculationAtIndex(i)->inputText(), result[i]) == 0); } } -QUIZ_CASE(calculation_store_ring_buffer) { + +QUIZ_CASE(calculation_store) { Shared::GlobalContext globalContext; CalculationStore store; - quiz_assert(CalculationStore::k_maxNumberOfCalculations == 10); - for (int i = 0; i < CalculationStore::k_maxNumberOfCalculations; i++) { + // Store is now {9, 8, 7, 6, 5, 4, 3, 2, 1, 0} + const char * result[] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"}; + for (int i = 0; i < 10; i++) { char text[2] = {(char)(i+'0'), 0}; store.push(text, &globalContext); quiz_assert(store.numberOfCalculations() == i+1); } - /* Store is now {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} */ - const char * result[10] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; assert_store_is(&store, result); - store.push("10", &globalContext); - /* Store is now {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} */ - const char * result1[10] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}; - assert_store_is(&store, result1); - for (int i = 9; i > 0; i = i-2) { store.deleteCalculationAtIndex(i); } - /* Store is now {1, 3, 5, 7, 9} */ - const char * result2[10] = {"1", "3", "5", "7", "9", "", "", "", "", ""}; + // Store is now {9, 7, 5, 3, 1} + const char * result2[] = {"9", "7", "5", "3", "1"}; assert_store_is(&store, result2); - for (int i = 5; i < CalculationStore::k_maxNumberOfCalculations; i++) { - char text[3] = {(char)(i+'0'), 0}; - store.push(text, &globalContext); - quiz_assert(store.numberOfCalculations() == i+1); - } - /* Store is now {0, 2, 4, 6, 8, 5, 6, 7, 8, 9} */ - const char * result3[10] = {"1", "3", "5", "7", "9", "5", "6", "7", "8", "9"}; - assert_store_is(&store, result3); - store.deleteAll(); } @@ -57,12 +43,12 @@ QUIZ_CASE(calculation_ans) { store.push("1+3/4", &globalContext); store.push("ans+2/3", &globalContext); - ::Calculation::Calculation * lastCalculation = store.calculationAtIndex(1); + Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store.calculationAtIndex(0); quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximate); quiz_assert(strcmp(lastCalculation->exactOutputText(),"29/12") == 0); store.push("ans+0.22", &globalContext); - lastCalculation = store.calculationAtIndex(2); + lastCalculation = store.calculationAtIndex(0); quiz_assert(lastCalculation->displayOutput(&globalContext) == ::Calculation::Calculation::DisplayOutput::ExactAndApproximateToggle); quiz_assert(strcmp(lastCalculation->approximateOutputText(),"2.6366666666667") == 0); @@ -71,7 +57,7 @@ QUIZ_CASE(calculation_ans) { void assertCalculationDisplay(const char * input, ::Calculation::Calculation::DisplayOutput display, ::Calculation::Calculation::EqualSign sign, const char * exactOutput, const char * approximateOutput, Context * context, CalculationStore * store) { store->push(input, context); - ::Calculation::Calculation * lastCalculation = store->calculationAtIndex(1); + Shared::ExpiringPointer<::Calculation::Calculation> lastCalculation = store->calculationAtIndex(0); quiz_assert(lastCalculation->displayOutput(context) == display); if (sign != ::Calculation::Calculation::EqualSign::Unknown) { quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(context) == sign);