From b0ede47d558e3d639371b788c8e27366fbb51a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 2 Mar 2018 15:19:35 +0100 Subject: [PATCH] [ion] Change Storage design (former kallax) --- apps/code/editor_controller.cpp | 8 +- apps/code/menu_controller.cpp | 40 ++-- apps/code/menu_controller.h | 5 +- apps/code/script.cpp | 31 +-- apps/code/script.h | 10 +- apps/code/script_parameter_controller.cpp | 4 +- apps/code/script_store.cpp | 35 +-- apps/code/script_store.h | 30 ++- escher/include/escher/text_field.h | 2 - escher/src/text_field.cpp | 8 - ion/Makefile | 3 +- ion/include/ion.h | 3 +- ion/include/ion/kallax.h | 44 ---- ion/include/ion/record.h | 51 ---- ion/include/ion/storage.h | 119 ++++++++++ ion/src/shared/kallax.cpp | 156 ------------- ion/src/shared/platform_info.cpp | 6 +- ion/src/shared/record.cpp | 71 ------ ion/src/shared/storage.cpp | 270 ++++++++++++++++++++++ 19 files changed, 475 insertions(+), 421 deletions(-) delete mode 100644 ion/include/ion/kallax.h delete mode 100644 ion/include/ion/record.h create mode 100644 ion/include/ion/storage.h delete mode 100644 ion/src/shared/kallax.cpp delete mode 100644 ion/src/shared/record.cpp create mode 100644 ion/src/shared/storage.cpp diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index 31462e707..d571e580c 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -5,13 +5,15 @@ #include #include +extern Ion::Storage storage; + namespace Code { EditorController::EditorController(MenuController * menuController) : ViewController(nullptr), m_textArea(this), m_areaBuffer(nullptr), - m_script(Ion::Record()), + m_script(Ion::Storage::Record()), m_menuController(menuController) { m_textArea.setDelegate(this); @@ -26,7 +28,7 @@ void EditorController::setScript(Script script) { m_script = script; const char * scriptBody = m_script.readContent(); size_t scriptBodySize = strlen(scriptBody)+1; - size_t availableScriptSize = scriptBodySize + Ion::Kallax::sharedKallax()->availableSize(); + size_t availableScriptSize = scriptBodySize + storage.availableSize(); assert(m_areaBuffer == nullptr); m_areaBuffer = new char[availableScriptSize]; strlcpy(m_areaBuffer, scriptBody, scriptBodySize); @@ -37,7 +39,7 @@ void EditorController::setScript(Script script) { bool EditorController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::OK || event == Ion::Events::Back) { Script::ErrorStatus err = m_script.writeContent(m_areaBuffer, strlen(m_areaBuffer)+1); - if (err == Script::ErrorStatus::NoEnoughSpaceAvailable) { + if (err == Script::ErrorStatus::NoEnoughSpaceAvailable || err == Script::ErrorStatus::RecordDoesNotExist) { assert(false); // This should not happen as we set the text area according to the available space in the Kallax } else { stackController()->pop(); diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index 985dd0e62..75fbde1e7 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -39,7 +39,6 @@ MenuController::MenuController(Responder * parentResponder, ScriptStore * script m_scriptCells[i].setParentResponder(&m_selectableTableView); m_scriptCells[i].editableTextCell()->textField()->setDelegate(this); m_scriptCells[i].editableTextCell()->textField()->setDraftTextBuffer(m_draftTextBuffer); - m_scriptCells[i].editableTextCell()->textField()->setTextBufferSize(Script::k_nameSize); m_scriptCells[i].editableTextCell()->textField()->setAlignment(0.0f, 0.5f); m_scriptCells[i].editableTextCell()->setMargins(0, 0, 0, Metric::HistoryHorizontalMargin); } @@ -127,9 +126,8 @@ void MenuController::renameSelectedScript() { void MenuController::deleteScript(Script script) { assert(!script.isNull()); - script.remove(); + script.destroy(); updateAddScriptRowDisplay(); - m_selectableTableView.reloadData(); } void MenuController::reloadConsole() { @@ -302,7 +300,7 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char } else { newName = text; } - Script::ErrorStatus error = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).rename(newName); + Script::ErrorStatus error = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).setName(newName); if (error == Script::ErrorStatus::None) { updateAddScriptRowDisplay(); textField->setText(newName); @@ -317,19 +315,15 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char app()->setFirstResponder(&m_selectableTableView); static_cast(const_cast(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default); return true; + } else if (error == Script::ErrorStatus::NameTaken) { + app()->displayWarning(I18n::Message::NameTaken); + } else if (error == Script::ErrorStatus::NonCompliantName) { + app()->displayWarning(I18n::Message::NonCompliantName); } else { - assert(error == Script::ErrorStatus::NameTaken); - // The name cannot be to long as the text field size was set accordingly - - // TODO: - // 2 solutions: - // 1. display a warning with - // app()->displayWarning(I18n::Message::ForbiddenValue); - // But that have to be done in textFieldDidReceiveEvent when textFieldShouldFinishEditing to avoid losing edition - // 2. Modify the name to take another one (add a number for instance). - // This would raise issues about the available space? We can't always add a character... - return false; + assert(error == Script::ErrorStatus::NoEnoughSpaceAvailable); + app()->displayWarning(I18n::Message::NameTooLong); } + return false; } bool MenuController::textFieldDidAbortEditing(TextField * textField, const char * text) { @@ -337,10 +331,15 @@ bool MenuController::textFieldDidAbortEditing(TextField * textField, const char // The previous text was an empty name. Use a numbered default script name. char numberedDefaultName[k_defaultScriptNameMaxSize]; numberedDefaultScriptName(numberedDefaultName); - Script::ErrorStatus error = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).rename(numberedDefaultName); + Script::ErrorStatus error = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).setName(numberedDefaultName); + if (error != Script::ErrorStatus::None) { + assert(false); + /* Because we use the numbered default name, the name should not be + * already taken. Plus, the script could be added only if the storage has + * enough available space to add a script named 'script99.py' */ + } assert(error == Script::ErrorStatus::None); updateAddScriptRowDisplay(); - m_selectableTableView.reloadData(); } m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow()); app()->setFirstResponder(&m_selectableTableView); @@ -357,13 +356,13 @@ bool MenuController::textFieldDidHandleEvent(TextField * textField, bool returnV } void MenuController::addScript() { - if (m_scriptStore->addNewScript()) { + Script::ErrorStatus error = m_scriptStore->addNewScript(); + if (error == Script::ErrorStatus::None) { updateAddScriptRowDisplay(); - m_selectableTableView.reloadData(); renameSelectedScript(); return; } - m_selectableTableView.reloadData(); + assert(false); // Adding a new script is called when !m_scriptStore.isFull() which guarantees that the available space in the storage is big enough } void MenuController::configureScript() { @@ -419,6 +418,7 @@ void MenuController::intToText(int i, char * buffer) { void MenuController::updateAddScriptRowDisplay() { m_shouldDisplayAddScriptRow = !m_scriptStore->isFull(); + m_selectableTableView.reloadData(); } } diff --git a/apps/code/menu_controller.h b/apps/code/menu_controller.h index 21ef51ef0..3f550316c 100644 --- a/apps/code/menu_controller.h +++ b/apps/code/menu_controller.h @@ -90,6 +90,9 @@ private: class EvenOddEditableTextCell : public ::EvenOddEditableTextCell { public: Responder * responder() override { + if (editableTextCell()->textField()->isEditing()) { + return this; + } return nullptr; } }; @@ -97,7 +100,7 @@ private: /* In the initializer list of the MenuController constructor, we initialize * m_scriptCells by copying k_maxNumberOfDisplayableScriptCells times the * constructor of an EvenOddEditableTextCell. */ - char m_draftTextBuffer[Script::k_nameSize]; + char m_draftTextBuffer[TextField::maxBufferSize()]; EvenOddCellWithEllipsis m_scriptParameterCells[k_maxNumberOfDisplayableScriptCells]; Shared::NewFunctionCell m_addNewScriptCell; EvenOddCell m_emptyCell; diff --git a/apps/code/script.cpp b/apps/code/script.cpp index 9c8fe7b69..6f0cd8ee2 100644 --- a/apps/code/script.cpp +++ b/apps/code/script.cpp @@ -9,29 +9,30 @@ Script::Script(Record f) : bool Script::importationStatus() const { assert(!isNull()); - const char * body = read(); - return (body[0] == 1); + Data d = value(); + return (((char *)d.buffer)[0] == 1); } void Script::toggleImportationStatus() { - assert(bodySize() >= 1); - m_body[0] = importationStatus() ? 0 : 1; + Data d = value(); + ((char *)d.buffer)[0] = (((char *)d.buffer)[0] == 1 ? 0 : 1); + setValue(d); } const char * Script::readContent() const { - const char * body = read(); - return body+k_importationStatusSize; + assert(!isNull()); + Data d = value(); + return (const char *)d.buffer+k_importationStatusSize; } -Script::ErrorStatus Script::writeContent(const char * data, size_t size) { - int deltaSize = (int)size+k_importationStatusSize - (int)bodySize(); - if (Ion::Kallax::sharedKallax()->moveNextRecord(start(), deltaSize)) { - *m_size += deltaSize; - strlcpy(m_body+k_importationStatusSize, data, size); - return ErrorStatus::None; - } else { - return ErrorStatus::NoEnoughSpaceAvailable; - } +Ion::Storage::Record::ErrorStatus Script::writeContent(const char * data, size_t size) { + // TODO: could we avoid this useless allocation? + char * buffer = new char[size+k_importationStatusSize]; + strlcpy(buffer+1, data, size); + buffer[0] = importationStatus() ? 1 : 0; + ErrorStatus e = setValue({.buffer= buffer, .size = size+k_importationStatusSize}); + delete[] buffer; + return e; } } diff --git a/apps/code/script.h b/apps/code/script.h index 5dfbde559..d944f05fd 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -5,13 +5,12 @@ namespace Code { -/* Record : | Total Size | Type | Name | Body | - * Script: | AutoImportationStatus | Content |*/ +/* Record : | Total Size | Name | Body | + * Script: | AutoImportationStatus | Content |*/ -class Script : public Ion::Record { -friend class ScriptStore; +class Script : public Ion::Storage::Record { public: - Script(Ion::Record f); + Script(Ion::Storage::Record r); bool importationStatus() const; void toggleImportationStatus(); @@ -19,7 +18,6 @@ public: const char * readContent() const; ErrorStatus writeContent(const char * data, size_t size); -private: constexpr static size_t k_importationStatusSize = 1; }; diff --git a/apps/code/script_parameter_controller.cpp b/apps/code/script_parameter_controller.cpp index cce0c4d89..fbf4c7d63 100644 --- a/apps/code/script_parameter_controller.cpp +++ b/apps/code/script_parameter_controller.cpp @@ -12,7 +12,7 @@ ScriptParameterController::ScriptParameterController(Responder * parentResponder m_deleteScript(I18n::Message::DeleteScript), m_selectableTableView(this, this, 0, 1, Metric::CommonTopMargin, Metric::CommonRightMargin, Metric::CommonBottomMargin, Metric::CommonLeftMargin, this), - m_script(Ion::Record()), + m_script(Ion::Storage::Record()), m_menuController(menuController) { } @@ -22,7 +22,7 @@ void ScriptParameterController::setScript(Script script){ } void ScriptParameterController::dismissScriptParameterController() { - m_script = Script(Ion::Record()); + m_script = Script(Ion::Storage::Record()); stackViewController()->pop(); } diff --git a/apps/code/script_store.cpp b/apps/code/script_store.cpp index 4b352f87a..53e7d7cd2 100644 --- a/apps/code/script_store.cpp +++ b/apps/code/script_store.cpp @@ -19,32 +19,14 @@ ScriptStore::ScriptStore() addScriptFromTemplate(ScriptTemplate::Polynomial()); } -Script ScriptStore::scriptAtIndex(int index) { - Ion::Record f = Ion::Kallax::sharedKallax()->recordOfTypeAtIndex(Ion::Record::Type::Script, index); - return Script(f); -} - -Script ScriptStore::scriptNamed(const char * name) { - Ion::Record f = Ion::Kallax::sharedKallax()->getRecord(Ion::Record::Type::Script, name); - return Script(f); -} - -int ScriptStore::numberOfScripts() { - return Ion::Kallax::sharedKallax()->numberOfRecordOfType(Ion::Record::Type::Script); -} - -bool ScriptStore::addNewScript() { - return addScriptFromTemplate(ScriptTemplate::Empty()); -} - void ScriptStore::deleteAllScripts() { for (int i = 0; i < numberOfScripts(); i++) { - scriptAtIndex(i).remove(); + scriptAtIndex(i).destroy(); } } bool ScriptStore::isFull() { - return (numberOfScripts() >= k_maxNumberOfScripts || Ion::Kallax::sharedKallax()->availableSize() < k_fullFreeSpaceSizeLimit); + return (numberOfScripts() >= k_maxNumberOfScripts || storage.availableSize() < k_fullFreeSpaceSizeLimit); } void ScriptStore::scanScriptsForFunctionsAndVariables(void * context, ScanCallback storeFunction, ScanCallback storeVariable) { @@ -137,18 +119,15 @@ const char * ScriptStore::contentOfScript(const char * name) { return script.readContent(); } -bool ScriptStore::addScriptFromTemplate(const ScriptTemplate * scriptTemplate) { +Script::ErrorStatus ScriptStore::addScriptFromTemplate(const ScriptTemplate * scriptTemplate) { size_t scriptSize = strlen(scriptTemplate->content())+1; char * body = new char[scriptSize+Script::k_importationStatusSize]; body[0] = 1; - strlcpy(body+1, scriptTemplate->content(), scriptSize); - bool result = false; - if (Ion::Kallax::sharedKallax()->sizeOfRecordWithBody(body) <= Ion::Kallax::sharedKallax()->availableSize()) { - Ion::Kallax::sharedKallax()->addRecord(scriptTemplate->name(), Ion::Record::Type::Script, body); - result = true; - } + strlcpy(body+Script::k_importationStatusSize, scriptTemplate->content(), scriptSize); + Script::ErrorStatus err = storage.createRecord(scriptTemplate->name(), body, scriptSize+Script::k_importationStatusSize); + assert(err != Script::ErrorStatus::NonCompliantName); delete[] body; - return result; + return err; } const char * ScriptStore::structID(mp_parse_node_struct_t *structNode) { diff --git a/apps/code/script_store.h b/apps/code/script_store.h index 4ef5b0042..db37bd48d 100644 --- a/apps/code/script_store.h +++ b/apps/code/script_store.h @@ -8,6 +8,8 @@ extern "C" { #include "py/parse.h" } +extern Ion::Storage storage; + namespace Code { class ScriptStore : public MicroPython::ScriptProvider { @@ -17,10 +19,18 @@ public: static constexpr int k_maxNumberOfScripts = 8; ScriptStore(); - Script scriptAtIndex(int index); - Script scriptNamed(const char * name); - int numberOfScripts(); - bool addNewScript(); + Script scriptAtIndex(int index) { + return Script(storage.recordWithExtensionAtIndex(k_scriptExtension, index)); + } + Script scriptNamed(const char * name) { + return Script(storage.recordNamed(name)); + } + int numberOfScripts() { + return storage.numberOfRecordsWithExtension(k_scriptExtension); + } + Ion::Storage::Record::ErrorStatus addNewScript() { + return addScriptFromTemplate(ScriptTemplate::Empty()); + } void deleteAllScripts(); bool isFull(); @@ -31,11 +41,15 @@ public: /* MicroPython::ScriptProvider */ const char * contentOfScript(const char * name) override; - bool addScriptFromTemplate(const ScriptTemplate * scriptTemplate); + Ion::Storage::Record::ErrorStatus addScriptFromTemplate(const ScriptTemplate * scriptTemplate); private: - // If the kallax free space has a size smaller than - // k_fullFreeSpaceSizeLimit, we consider the script store as full. - static constexpr int k_fullFreeSpaceSizeLimit = Ion::Record::k_sizeSize+Ion::Record::k_nameSize+Ion::Record::k_typeSize+10; + /* If the storage available space has a smaller size than + * k_fullFreeSpaceSizeLimit, we consider the script store as full. + * To be able to add a new empty record, the available space should at least + * stores a Script with default name "script99.py" (12 char), the importation + * status (1 char), the default content "from math import *\n" (20 char) and + * 10 char of free space. */ + static constexpr int k_fullFreeSpaceSizeLimit = sizeof(Ion::Storage::record_size_t)+12+1+20+10; static constexpr size_t k_fileInput2ParseNodeStructKind = 1; static constexpr size_t k_functionDefinitionParseNodeStructKind = 3; static constexpr size_t k_expressionStatementParseNodeStructKind = 5; diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index 3170d9960..99c0b6178 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -12,7 +12,6 @@ public: float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor = KDColorWhite); void setDelegate(TextFieldDelegate * delegate) { m_delegate = delegate; } void setDraftTextBuffer(char * draftTextBuffer); - void setTextBufferSize(size_t size); bool isEditing() const; size_t draftTextLength() const; void setText(const char * text); @@ -31,7 +30,6 @@ protected: public: ContentView(char * textBuffer, char * draftTextBuffer, size_t textBufferSize, KDText::FontSize size, float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor = KDColorWhite); void setDraftTextBuffer(char * draftTextBuffer); - void setTextBufferSize(size_t size); void drawRect(KDContext * ctx, KDRect rect) const override; bool isEditing() const { return m_isEditing; } const char * text() const override; diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index 172f65868..1ec07be5a 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -22,10 +22,6 @@ void TextField::ContentView::setDraftTextBuffer(char * draftTextBuffer) { m_draftTextBuffer = draftTextBuffer; } -void TextField::ContentView::setTextBufferSize(size_t size) { - m_textBufferSize = size; -} - void TextField::ContentView::drawRect(KDContext * ctx, KDRect rect) const { KDColor bckCol = m_backgroundColor; if (m_isEditing) { @@ -176,10 +172,6 @@ void TextField::setDraftTextBuffer(char * draftTextBuffer) { m_contentView.setDraftTextBuffer(draftTextBuffer); } -void TextField::setTextBufferSize(size_t size) { - m_contentView.setTextBufferSize(size); -} - bool TextField::isEditing() const { return m_contentView.isEditing(); } diff --git a/ion/Makefile b/ion/Makefile index ac20803fa..2dc0d3f4c 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -18,9 +18,8 @@ ion/src/shared/platform_info.o: SFLAGS += -DPATCH_LEVEL="$(call initializer_list objs += $(addprefix ion/src/shared/, \ events.o \ - kallax.o \ - record.o \ platform_info.o \ + storage.o \ ) tests += $(addprefix ion/test/,\ diff --git a/ion/include/ion.h b/ion/include/ion.h index a066c24ef..ac38e587d 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -7,11 +7,10 @@ #include #include #include -#include #include #include #include -#include +#include #include #include #include diff --git a/ion/include/ion/kallax.h b/ion/include/ion/kallax.h deleted file mode 100644 index ef56c6dca..000000000 --- a/ion/include/ion/kallax.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef ION_KALLAX_H -#define ION_KALLAX_H - -#include - -/* Kallax : | Magic | Record1 | Record2 | ... | MagicĀ | - * | Magic | Size1 | Type1 | Name1 | BodySize1 | Body1 | Size2 | Type2 | Name2 | BodySize2 | Body2 | ... | Magic */ - -namespace Ion { - -class Kallax { -public: - Kallax(); - static Kallax * sharedKallax(); - int numberOfRecordOfType(Record::Type type); - Record recordOfTypeAtIndex(Record::Type type, int index); - Record getRecord(Record::Type type, const char * name); - - Record addRecord(const char * name, Record::Type type, const char * content); - - // availableSize takes into account the the size of the last Record must be 0. - size_t availableSize(); - - bool isNameTaken(const char * name, Record::Type type); - bool moveNextRecord(char * start, int delta); - size_t sizeOfRecordWithBody(const char * body) const; -private: - // lastUsedData takes into account the the size of the last Record must be 0. - char * lastUsedData(); - size_t * sizeAddressOfRecordStarting(char * start) const; - size_t sizeOfRecordStarting(char * start) const; - Record::Type typeOfRecordStarting(char * start) const; - char * nameOfRecordStarting(char * start); - char * bodyOfRecordStarting(char * start); - constexpr static size_t k_totalSize = 4096; - constexpr static uint32_t Magic = 0xDECA0DF0; - uint32_t m_dataHeader; - char m_data[k_totalSize]; - uint32_t m_dataFooter; -}; - -} - -#endif diff --git a/ion/include/ion/record.h b/ion/include/ion/record.h deleted file mode 100644 index 570f47e4a..000000000 --- a/ion/include/ion/record.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef ION_RECORD_H -#define ION_RECORD_H - -#include -#include - -/* Record : | Total Size | Type | Name | Body | */ - -namespace Ion { - -class Record { -public: - enum class Type : uint8_t { - Null, - Script - }; - enum class ErrorStatus { - None = 0, - NameTaken = 1, - NameTooLong = 2, - NoEnoughSpaceAvailable = 3 - }; - Record(size_t * totalSize = nullptr, char * name = nullptr, Type type = Type::Null, char * body = nullptr); - - bool isNull() const; - - const char * name() const; - ErrorStatus rename(const char * newName); // May fail if name taken or name too long - - const char * read() const; - ErrorStatus write(const char * data, size_t size = 0); // May fail if no more space is available - - Type type(); - - void remove(); // Will always succeed - constexpr static size_t k_nameSize = 50; - constexpr static size_t k_sizeSize = sizeof(size_t); - constexpr static size_t k_typeSize = sizeof(Type); -protected: - size_t bodySize() const; - char * start(); - char * m_body; - size_t * m_size; -private: - char * m_name; - Type m_type; -}; - -} - -#endif diff --git a/ion/include/ion/storage.h b/ion/include/ion/storage.h new file mode 100644 index 000000000..324799719 --- /dev/null +++ b/ion/include/ion/storage.h @@ -0,0 +1,119 @@ +#ifndef ION_STORAGE_H +#define ION_STORAGE_H + +#include + +namespace Ion { + +class Storage; + +} + +extern Ion::Storage storage; + +namespace Ion { + +/* Storage : | Magic | Record1 | Record2 | ... | MagicĀ | + * | Magic | Size1(uint16_t) | Name1 | Body1 | Size2(uint16_t) | Name2 | Body2 | ... | Magic */ + +class Storage { +public: + class Record { + friend class Storage; + public: + enum class ErrorStatus { + None = 0, + NameTaken = 1, + NonCompliantName = 2, + NoEnoughSpaceAvailable = 3, + RecordDoesNotExist = 4 + }; + struct Data + { + const void * buffer; + size_t size; + }; + Record(const char * name = nullptr); + bool operator==(const Record & other) const { + return m_nameCRC32 == other.m_nameCRC32; + } + bool isNull() const { + return m_nameCRC32 == 0; + } + const char * name() const { + return storage.nameOfRecord(*this); + } + ErrorStatus setName(const char * name) { + return storage.setNameOfRecord(*this, name); + } + Data value() const { + return storage.valueOfRecord(*this); + } + ErrorStatus setValue(Data data) { + return storage.setValueOfRecord(*this, data); + } + void destroy() { + return storage.destroyRecord(*this); + } + private: + uint32_t m_nameCRC32; + }; + Storage(); + size_t availableSize(); + Record::ErrorStatus createRecord(const char * name, const void * data, size_t size); + int numberOfRecordsWithExtension(const char * extension); + Record recordWithExtensionAtIndex(const char * extension, int index); + Record recordNamed(const char * name); + typedef uint16_t record_size_t; +private: + constexpr static uint32_t Magic = 0xDECA0DF0; + constexpr static size_t k_storageSize = 4096; + constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8); + + /* Getters/Setters on recordID */ + const char * nameOfRecord(const Record record); + Record::ErrorStatus setNameOfRecord(const Record record, const char * name); + Record::Data valueOfRecord(const Record record); + Record::ErrorStatus setValueOfRecord(const Record record, Record::Data data); + void destroyRecord(const Record record); + + /* Getters on address in buffer */ + record_size_t sizeOfRecordStarting(char * start) const; + const char * nameOfRecordStarting(char * start) const; + const void * valueOfRecordStarting(char * start) const; + + /* Overriders */ + size_t overrideSizeAtPosition(char * position, record_size_t size); + size_t overrideNameAtPosition(char * position, const char * name); + size_t overrideValueAtPosition(char * position, const void * data, record_size_t size); + + bool isNameTaken(const char * name, Record * recordToExclude = nullptr); + bool nameCompliant(const char * name) const; + char * endBuffer(); + size_t sizeOfRecord(const char * name, size_t size) const; + bool slideBuffer(char * position, int delta); + class RecordIterator { + public: + RecordIterator(char * start) : m_recordStart(start) {} + char * operator*() { return m_recordStart; } + RecordIterator& operator++(); + bool operator!=(const RecordIterator& it) const { return m_recordStart != it.m_recordStart; } + private: + char * m_recordStart; + }; + RecordIterator begin() const { + if (sizeOfRecordStarting((char *)m_buffer) == 0) { + return nullptr; + } + return RecordIterator((char *)m_buffer); + }; + RecordIterator end() const { return RecordIterator(nullptr); }; + + uint32_t m_magicHeader; + char m_buffer[k_storageSize]; + uint32_t m_magicFooter; +}; + +} + +#endif diff --git a/ion/src/shared/kallax.cpp b/ion/src/shared/kallax.cpp deleted file mode 100644 index 82ae852bf..000000000 --- a/ion/src/shared/kallax.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include -#include -#include - -Ion::Kallax kallax; - -namespace Ion { - -Kallax::Kallax() : - m_dataHeader(Magic), - m_data(), - m_dataFooter(Magic) -{ - size_t * p = (size_t *)m_data; - p[0] = 0; -} - -Kallax * Kallax::sharedKallax() { - return &kallax; -} - -int Kallax::numberOfRecordOfType(Record::Type type) { - assert(m_dataHeader == Magic); - assert(m_dataFooter == Magic); - int count = 0; - char * currentPointer = m_data; - size_t size = sizeOfRecordStarting(currentPointer); - while (size != 0 && currentPointer < m_data + k_totalSize) { - if (typeOfRecordStarting(currentPointer) == type) { - count++; - } - currentPointer += size; - size = sizeOfRecordStarting(currentPointer); - } - return count; -} - -Record Kallax::recordOfTypeAtIndex(Record::Type type, int index) { - int currentIndex = -1; - char * currentPointer = m_data; - size_t size = sizeOfRecordStarting(currentPointer); - while (size != 0 && currentPointer < m_data + k_totalSize) { - if (typeOfRecordStarting(currentPointer) == type) { - currentIndex++; - if (currentIndex == index) { - break; - } - } - currentPointer += size; - size = sizeOfRecordStarting(currentPointer); - } - return Record(sizeAddressOfRecordStarting(currentPointer), nameOfRecordStarting(currentPointer), type, bodyOfRecordStarting(currentPointer)); -} - -Record Kallax::getRecord(Record::Type type, const char * name) { - for (int i = 0; i < numberOfRecordOfType(type); i++) { - Record currentRecord = recordOfTypeAtIndex(type, i); - if (strcmp(currentRecord.name(), name) == 0) { - return currentRecord; - } - } - return Record(); -} - -Record Kallax::addRecord(const char * name, Record::Type type, const char * body) { - // assert name is short enough and there is enough space to add the record - assert(strlen(name) < Record::k_nameSize); - assert(availableSize() >= sizeOfRecordWithBody(body)); - // Find the end of data - char * currentPointer = m_data; - size_t size = sizeOfRecordStarting(currentPointer); - while (size != 0 && currentPointer < m_data + k_totalSize) { - currentPointer += size; - size = sizeOfRecordStarting(currentPointer); - } - size_t recordSize = sizeOfRecordWithBody(body); - // Fill totalSize - *((size_t *)currentPointer) = recordSize; - // Fill type - *(currentPointer+Record::k_sizeSize) = (uint8_t)type; - // Fill name - strlcpy(currentPointer+Record::k_sizeSize+Record::k_typeSize, name, Record::k_nameSize); - // Fill body - strlcpy(currentPointer+Record::k_sizeSize+Record::k_typeSize+Record::k_nameSize, body, strlen(body)+1); - char * nextPointer = currentPointer + recordSize; - *((size_t *)nextPointer) = 0; - return Record(sizeAddressOfRecordStarting(currentPointer), nameOfRecordStarting(currentPointer), type, bodyOfRecordStarting(currentPointer)); -} - -char * Kallax::lastUsedData() { - size_t usedSize = 0; - char * currentPointer = m_data; - size_t size = sizeOfRecordStarting(currentPointer); - while (size != 0 && currentPointer < m_data + k_totalSize) { - usedSize += size; - currentPointer += size; - size = sizeOfRecordStarting(currentPointer); - } - return currentPointer + Record::k_sizeSize; -} - - -size_t Kallax::availableSize() { - return k_totalSize-(lastUsedData()-m_data); -} - -bool Kallax::isNameTaken(const char * name, Record::Type type) { - char * currentPointer = m_data; - size_t size = sizeOfRecordStarting(currentPointer); - while (size != 0 && currentPointer < m_data + k_totalSize) { - if (typeOfRecordStarting(currentPointer) == type && strcmp(nameOfRecordStarting(currentPointer), name) == 0) { - return true; - } - currentPointer += size; - size = sizeOfRecordStarting(currentPointer); - } - return false; -} - -bool Kallax::moveNextRecord(char * start, int delta) { - if (delta > (int)availableSize()) { - return false; - } - char * nextRecord = start + sizeOfRecordStarting(start); - memmove(nextRecord+delta, nextRecord, lastUsedData()-nextRecord); - return true; -} - -size_t * Kallax::sizeAddressOfRecordStarting(char * start) const { - return (size_t *)start; -} - -size_t Kallax::sizeOfRecordStarting(char * start) const { - if (start >= m_data + k_totalSize) { - return 0; - } - return *(sizeAddressOfRecordStarting(start)); -} - -Record::Type Kallax::typeOfRecordStarting(char * start) const { - return (Record::Type)*((uint8_t *)start+Record::k_sizeSize); -} - -char * Kallax::nameOfRecordStarting(char * start) { - return start+Record::k_sizeSize+Record::k_typeSize; -} - -char * Kallax::bodyOfRecordStarting(char * start) { - return start+Record::k_sizeSize+Record::k_typeSize+Record::k_nameSize; -} - -size_t Kallax::sizeOfRecordWithBody(const char * body) const { - return Record::k_sizeSize+Record::k_typeSize+Record::k_nameSize+strlen(body)+1; -} - -} diff --git a/ion/src/shared/platform_info.cpp b/ion/src/shared/platform_info.cpp index 870e27965..263d935d5 100644 --- a/ion/src/shared/platform_info.cpp +++ b/ion/src/shared/platform_info.cpp @@ -20,7 +20,7 @@ #define FORCE_LINK #endif -extern Ion::Kallax kallax; +extern Ion::Storage storage; class PlatformInfo { public: @@ -28,14 +28,16 @@ public: m_header(Magic), m_version{EPSILON_VERSION}, m_patchLevel{PATCH_LEVEL}, - m_storageAddress(&kallax), + m_storageAddress(&storage), m_footer(Magic) { } const char * version() const { + assert(m_storageAddress != nullptr); assert(m_header == Magic); assert(m_footer == Magic); return m_version; } const char * patchLevel() const { + assert(m_storageAddress != nullptr); assert(m_header == Magic); assert(m_footer == Magic); return m_patchLevel; diff --git a/ion/src/shared/record.cpp b/ion/src/shared/record.cpp deleted file mode 100644 index 2f240bf45..000000000 --- a/ion/src/shared/record.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include -#include -#include -#include - -namespace Ion { - -Record::Record(size_t * size, char * name, Type type, char * body) : - m_body(body), - m_size(size), - m_name(name), - m_type(type) -{ -} - -bool Record::isNull() const { - if (m_type == Type::Null) { - assert(m_size == nullptr); - return true; - } - return false; -} - - -const char * Record::name() const { - return m_name; -} - -Record::ErrorStatus Record::rename(const char * newName) { - if (Kallax::sharedKallax()->isNameTaken(newName, m_type)) { - return ErrorStatus::NameTaken; - } - if (strlen(newName) >= k_nameSize) { - return ErrorStatus::NameTooLong; - } - strlcpy(m_name, newName, k_nameSize); - return ErrorStatus::None; -} - -const char * Record::read() const { - return m_body; -} - -Record::ErrorStatus Record::write(const char * data, size_t size) { - int deltaSize = (int)size - (int)bodySize(); - // TODO: if this fails because deltaSize is too big, return an error? - if (Kallax::sharedKallax()->moveNextRecord(start(), deltaSize)) { - *m_size += deltaSize; - strlcpy(m_body, data, size); - return ErrorStatus::None; - } - return ErrorStatus::NoEnoughSpaceAvailable; -} - -Record::Type Record::type() { - return m_type; -} - -void Record::remove() { - Kallax::sharedKallax()->moveNextRecord(start(), -*(m_size)); -} - -char * Record::start() { - return m_name - k_typeSize - k_sizeSize; -} - -size_t Record::bodySize() const { - return *m_size - k_nameSize - k_typeSize - k_sizeSize; -} - -} diff --git a/ion/src/shared/storage.cpp b/ion/src/shared/storage.cpp new file mode 100644 index 000000000..7ae865d1f --- /dev/null +++ b/ion/src/shared/storage.cpp @@ -0,0 +1,270 @@ +#include +#include +#include + +Ion::Storage storage; + +namespace Ion { + +Storage::Record::Record(const char * name) { + if (name == nullptr) { + m_nameCRC32 = 0; + return; + } + size_t lenght = strlen(name); + size_t crc32InputSize = lenght*sizeof(char)/sizeof(uint32_t)+1; + uint32_t * crc32Input = new uint32_t[crc32InputSize]; + memset(crc32Input, 0, crc32InputSize*sizeof(uint32_t)); + strlcpy((char *)crc32Input, name, lenght+1); + assert((crc32InputSize*sizeof(uint32_t) & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 + m_nameCRC32 = Ion::crc32(crc32Input, crc32InputSize); +} + +Storage::Storage() : + m_magicHeader(Magic), + m_buffer(), + m_magicFooter(Magic) +{ + assert(m_magicHeader == Magic); + assert(m_magicFooter == Magic); + // Set the size of the first record to 0 + overrideSizeAtPosition(m_buffer, 0); +} + +size_t Storage::availableSize() { + return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t); +} + +Storage::Record::ErrorStatus Storage::createRecord(const char * name, const void * data, size_t size) { + if (!nameCompliant(name)) { + return Record::ErrorStatus::NonCompliantName; + } + size_t recordSize = sizeOfRecord(name, size); + if (recordSize >= k_maxRecordSize || recordSize > availableSize()) { + return Record::ErrorStatus::NoEnoughSpaceAvailable; + } + if (isNameTaken(name)) { + return Record::ErrorStatus::NameTaken; + } + // Find the end of data + char * newRecord = endBuffer(); + // Fill totalSize + newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize); + // Fill name + newRecord += overrideNameAtPosition(newRecord, name); + // Fill data + newRecord += overrideValueAtPosition(newRecord, data, size); + // Next Record is null-sized + overrideSizeAtPosition(newRecord, 0); + return Record::ErrorStatus::None; +} + +int Storage::numberOfRecordsWithExtension(const char * extension) { + int count = 0; + for (char * p : *this) { + const char * name = nameOfRecordStarting(p); + const char * ext = name+strlen(name)-strlen(extension); + if (strcmp(ext, extension) == 0) { + count++; + } + } + return count; +} + +Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int index) { + int currentIndex = -1; + const char * name = nullptr; + for (char * p : *this) { + const char * currentName = nameOfRecordStarting(p); + const char * currentExtension = currentName+strlen(currentName)-strlen(extension); + if (strcmp(currentExtension, extension) == 0) { + currentIndex++; + } + if (currentIndex == index) { + name = currentName; + break; + } + } + if (name == nullptr) { + return Record(); + } + return Record(name); +} + +Storage::Record Storage::recordNamed(const char * name) { + for (char * p : *this) { + const char * currentName = nameOfRecordStarting(p); + if (strcmp(currentName, name) == 0) { + return Record(name); + } + } + return Record(); +} + +const char * Storage::nameOfRecord(const Record record) { + for (char * p : *this) { + Record currentRecord(nameOfRecordStarting(p)); + if (record == currentRecord) { + return nameOfRecordStarting(p); + } + } + return nullptr; +} + +Storage::Record::ErrorStatus Storage::setNameOfRecord(Record record, const char * name) { + if (!nameCompliant(name)) { + return Record::ErrorStatus::NonCompliantName; + } + if (isNameTaken(name, &record)) { + return Record::ErrorStatus::NameTaken; + } + size_t nameSize = strlen(name)+1; + for (char * p : *this) { + Record currentRecord(nameOfRecordStarting(p)); + if (record == currentRecord) { + size_t previousNameSize = strlen(nameOfRecordStarting(p))+1; + record_size_t previousRecordSize = sizeOfRecordStarting(p); + size_t newRecordSize = previousRecordSize-previousNameSize+nameSize; + if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) { + return Record::ErrorStatus::NoEnoughSpaceAvailable; + } + overrideSizeAtPosition(p, newRecordSize); + overrideNameAtPosition(p+sizeof(record_size_t), name); + return Record::ErrorStatus::None; + } + } + return Record::ErrorStatus::RecordDoesNotExist; +} + +Storage::Record::Data Storage::valueOfRecord(const Record record) { + for (char * p : *this) { + Record currentRecord(nameOfRecordStarting(p)); + if (record == currentRecord) { + const char * name = nameOfRecordStarting(p); + record_size_t size = sizeOfRecordStarting(p); + const void * value = valueOfRecordStarting(p); + return {.buffer= value, .size= size-strlen(name)-1-sizeof(record_size_t)}; + } + } + return {.buffer= nullptr, .size= 0}; +} + +Storage::Record::ErrorStatus Storage::setValueOfRecord(Record record, Record::Data data) { + for (char * p : *this) { + Record currentRecord(nameOfRecordStarting(p)); + if (record == currentRecord) { + record_size_t previousRecordSize = sizeOfRecordStarting(p); + const char * name = nameOfRecordStarting(p); + size_t newRecordSize = sizeOfRecord(name, data.size); + if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+previousRecordSize, newRecordSize-previousRecordSize)) { + return Record::ErrorStatus::NoEnoughSpaceAvailable; + } + record_size_t nameSize = strlen(name)+1; + overrideSizeAtPosition(p, newRecordSize); + overrideValueAtPosition(p+sizeof(record_size_t)+nameSize, data.buffer, data.size); + return Record::ErrorStatus::None; + } + } + return Record::ErrorStatus::RecordDoesNotExist; +} + +void Storage::destroyRecord(Record record) { + for (char * p : *this) { + Record currentRecord(nameOfRecordStarting(p)); + if (record == currentRecord) { + record_size_t previousRecordSize = sizeOfRecordStarting(p); + slideBuffer(p+previousRecordSize, -previousRecordSize); + } + } +} + +Storage::record_size_t Storage::sizeOfRecordStarting(char * start) const { + return *((record_size_t *)start); +} + +const char * Storage::nameOfRecordStarting(char * start) const { + return start+sizeof(record_size_t); +} + +const void * Storage::valueOfRecordStarting(char * start) const { + char * currentChar = start+sizeof(record_size_t); + while (*currentChar != 0) { + currentChar++; + } + return currentChar+1; +} + +size_t Storage::overrideSizeAtPosition(char * position, record_size_t size) { + *((record_size_t *)position) = size; + return sizeof(record_size_t); +} + +size_t Storage::overrideNameAtPosition(char * position, const char * name) { + return strlcpy(position, name, strlen(name)+1)+1; +} + +size_t Storage::overrideValueAtPosition(char * position, const void * data, record_size_t size) { + memcpy(position, data, size); + return size; +} + +bool Storage::isNameTaken(const char * name, Record * recordToExclude) { + Record r = Record(name); + if (r == Record()) { + return true; + } + for (char * p : *this) { + Record s(nameOfRecordStarting(p)); + if (recordToExclude && s == *recordToExclude) { + continue; + } + if (s == r) { + return true; + } + } + return false; +} + +bool Storage::nameCompliant(const char * name) const { + /* The name format is [a-zA-Z0-9.]+ */ + const char * currentChar = name; + while (*currentChar != 0) { + if ((*currentChar >= 'a' && *currentChar <= 'z') || (*currentChar >= 'A' && *currentChar <= 'Z') || (*currentChar >= '0' && *currentChar <= '9') || *currentChar == '.') { + currentChar++; + continue; + } + return false; + } + return true; +} + +char * Storage::endBuffer() { + char * currentBuffer = m_buffer; + for (char * p : *this) { + currentBuffer += sizeOfRecordStarting(p); + } + return currentBuffer; +} + +size_t Storage::sizeOfRecord(const char * name, size_t dataSize) const { + size_t nameSize = strlen(name)+1; + return nameSize+dataSize+sizeof(record_size_t); +} + +bool Storage::slideBuffer(char * position, int delta) { + if (delta > (int)availableSize()) { + return false; + } + memmove(position+delta, position, endBuffer()+sizeof(record_size_t)-position); + return true; +} + +Storage::RecordIterator & Storage::RecordIterator::operator++() { + record_size_t size = *((record_size_t *)m_recordStart); + char * nextRecord = m_recordStart+size; + record_size_t newRecordSize = *((record_size_t *)nextRecord); + m_recordStart = (newRecordSize == 0 ? nullptr : nextRecord); + return *this; +} + +}