1
0
mirror of https://github.com/s00500/ESPUI.git synced 2024-11-21 22:50:55 +00:00

Reworked control marshaling code to be aware of and enforce size limits by starting to fragment sooner.

This commit is contained in:
MartinMueller2003 2024-03-05 13:40:01 -05:00
parent e6e4d6b09c
commit f2146309fe
4 changed files with 111 additions and 52 deletions

View File

@ -41,7 +41,8 @@ platform = espressif32
board = esp32dev board = esp32dev
monitor_filters = esp32_exception_decoder monitor_filters = esp32_exception_decoder
board_build.flash_mode = dout board_build.flash_mode = dout
;build_flags = build_flags =
-D TEST_HUGE_TEXT
; -D DEBUG_ESPUI ; -D DEBUG_ESPUI
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}

View File

@ -268,9 +268,11 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
#endif // def ESP32 #endif // def ESP32
// Serial.println(String("prepareJSONChunk: Start. InUpdateMode: ") + String(InUpdateMode)); // Serial.println(String("prepareJSONChunk: Start. InUpdateMode: ") + String(InUpdateMode));
// Serial.println(String("prepareJSONChunk: Start. startindex: ") + String(startindex));
// Serial.println(String("prepareJSONChunk: Start. FragmentRequestString: '") + FragmentRequestString + "'");
int elementcount = 0; int elementcount = 0;
uint32_t MaxEstimatedMarshaledJsonSize = (!InUpdateMode) ? ESPUI.jsonInitialDocumentSize: ESPUI.jsonUpdateDocumentSize; uint32_t MaxMarshaledJsonSize = (!InUpdateMode) ? ESPUI.jsonInitialDocumentSize: ESPUI.jsonUpdateDocumentSize;
uint32_t TotalEstimatedMarshaledJsonSize = 0; uint32_t EstimatedUsedMarshaledJsonSize = 0;
do // once do // once
{ {
@ -388,21 +390,32 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
} }
} }
TotalEstimatedMarshaledJsonSize += control->GetEstimatedMarshaledSize(); // Serial.println(String(F("prepareJSONChunk: MaxMarshaledJsonSize: ")) + String(MaxMarshaledJsonSize));
bool DocWillOverflow = TotalEstimatedMarshaledJsonSize >= MaxEstimatedMarshaledJsonSize; // Serial.println(String(F("prepareJSONChunk: Cur EstimatedUsedMarshaledJsonSize: ")) + String(EstimatedUsedMarshaledJsonSize));
JsonObject item = items.createNestedObject(); JsonObject item = items.createNestedObject();
elementcount++; elementcount++;
if(!DocWillOverflow) uint32_t RemainingSpace = (MaxMarshaledJsonSize - EstimatedUsedMarshaledJsonSize) - 100;
{ // Serial.println(String(F("prepareJSONChunk: RemainingSpace: ")) + String(RemainingSpace));
control->MarshalControl(item, InUpdateMode, DataOffset); uint32_t SpaceUsedByMarshaledControl = 0;
} bool ControlIsFragmented = control->MarshalControl(item,
InUpdateMode,
DataOffset,
RemainingSpace,
SpaceUsedByMarshaledControl);
// Serial.println(String(F("prepareJSONChunk: SpaceUsedByMarshaledControl: ")) + String(SpaceUsedByMarshaledControl));
EstimatedUsedMarshaledJsonSize += SpaceUsedByMarshaledControl;
// Serial.println(String(F("prepareJSONChunk: New EstimatedUsedMarshaledJsonSize: ")) + String(EstimatedUsedMarshaledJsonSize));
// Serial.println(String(F("prepareJSONChunk: ControlIsFragmented: ")) + String(ControlIsFragmented));
if (DocWillOverflow || (ESPUI.jsonChunkNumberMax > 0 && (elementcount % ESPUI.jsonChunkNumberMax) == 0)) // did the control get added to the doc?
if (0 == SpaceUsedByMarshaledControl ||
(ESPUI.jsonChunkNumberMax > 0 && (elementcount % ESPUI.jsonChunkNumberMax) == 0))
{ {
// String("prepareJSONChunk: too much data in the message. Remove the last entry"); // Serial.println( String("prepareJSONChunk: too much data in the message. Remove the last entry"));
if (1 == elementcount) if (1 == elementcount)
{ {
Serial.println(String(F("ERROR: prepareJSONChunk: Control ")) + String(control->id) + F(" is too large to be sent to the browser.")); // Serial.println(String(F("prepareJSONChunk: Control ")) + String(control->id) + F(" is too large to be sent to the browser."));
// Serial.println(String(F("ERROR: prepareJSONChunk: value: ")) + control->value); // Serial.println(String(F("ERROR: prepareJSONChunk: value: ")) + control->value);
rootDoc.clear(); rootDoc.clear();
item = items.createNestedObject(); item = items.createNestedObject();
@ -420,13 +433,16 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
// exit the loop // exit the loop
control = nullptr; control = nullptr;
} }
else if (SingleControl) else if ((SingleControl) ||
(ControlIsFragmented) ||
(MaxMarshaledJsonSize < (EstimatedUsedMarshaledJsonSize + 100)))
{ {
// Serial.println("prepareJSONChunk: exit loop"); // Serial.println("prepareJSONChunk: Doc is Full, Fragmented Control or Single Control. exit loop");
control = nullptr; control = nullptr;
} }
else else
{ {
// Serial.println("prepareJSONChunk: Next Control");
control = control->next; control = control->next;
} }
} // end while (control != nullptr) } // end while (control != nullptr)
@ -437,7 +453,7 @@ uint32_t ESPUIclient::prepareJSONChunk(uint16_t startindex,
xSemaphoreGive(ESPUI.ControlsSemaphore); xSemaphoreGive(ESPUI.ControlsSemaphore);
#endif // def ESP32 #endif // def ESP32
// Serial.println(String("prepareJSONChunk: elementcount: ") + String(elementcount)); // Serial.println(String("prepareJSONChunk: END: elementcount: ") + String(elementcount));
return elementcount; return elementcount;
} }

View File

@ -19,7 +19,6 @@ Control::Control(ControlType type, const char* label, std::function<void(Control
{ {
id = ++idCounter; id = ++idCounter;
ControlChangeID = 1; ControlChangeID = 1;
EstimateMarshaledSize();
} }
Control::Control(const Control& Control) Control::Control(const Control& Control)
@ -32,8 +31,7 @@ Control::Control(const Control& Control)
visible(Control.visible), visible(Control.visible),
parentControl(Control.parentControl), parentControl(Control.parentControl),
next(Control.next), next(Control.next),
ControlChangeID(Control.ControlChangeID), ControlChangeID(Control.ControlChangeID)
EstimatedMarshaledSize(Control.EstimatedMarshaledSize)
{ } { }
void Control::SendCallback(int type) void Control::SendCallback(int type)
@ -50,20 +48,70 @@ void Control::DeleteControl()
callback = nullptr; callback = nullptr;
} }
void Control::MarshalControl(JsonObject & _item, bool refresh, uint32_t StartingOffset) bool Control::MarshalControl(JsonObject & _item,
bool refresh,
uint32_t StartingOffset,
uint32_t AvailMarshaledLength,
uint32_t &EstimatedMarshaledLength)
{ {
JsonObject & item = _item; // this code assumes MaxMarshaledLength > JsonMarshalingRatio
uint32_t length = value.length(); // Serial.println(String("MarshalControl: StartingOffset: ") + String(StartingOffset));
uint32_t maxLength = uint32_t(double(ESPUI.jsonInitialDocumentSize) * 0.75); // Serial.println(String("MarshalControl: AvailMarshaledLength: ") + String(AvailMarshaledLength));
if((length > maxLength) || StartingOffset) // Serial.println(String("MarshalControl: Control ID: ") + String(id));
{
/*
Serial.println(String("MarshalControl:Start Fragment Processing"));
Serial.println(String("MarshalControl:id: ") + String(id));
Serial.println(String("MarshalControl:length: ") + String(length));
Serial.println(String("MarshalControl:StartingOffset: ") + String(StartingOffset));
Serial.println(String("MarshalControl:maxLength: ") + String(maxLength));
bool ControlIsFragmented = false;
// create a new item in the response document
JsonObject & item = _item;
// how much space do we expect to use?
uint32_t ValueMarshaledLength = (value.length() - StartingOffset) * JsonMarshalingRatio;
uint32_t LabelMarshaledLength = strlen(label) * JsonMarshalingRatio;
uint32_t MinimumMarshaledLength = LabelMarshaledLength + JsonMarshaledOverhead;
uint32_t MaximumMarshaledLength = ValueMarshaledLength + MinimumMarshaledLength;
uint32_t SpaceForMarshaledValue = AvailMarshaledLength - MinimumMarshaledLength;
// Serial.println(String("MarshalControl: value.length(): ") + String(value.length()));
// Serial.println(String("MarshalControl: ValueMarshaledLength: ") + String(ValueMarshaledLength));
// Serial.println(String("MarshalControl: LabelMarshaledLength: ") + String(LabelMarshaledLength));
// Serial.println(String("MarshalControl: MaximumMarshaledLength: ") + String(MaximumMarshaledLength));
// Serial.println(String("MarshalControl: MinimumMarshaledLength: ") + String(MinimumMarshaledLength));
// Serial.println(String("MarshalControl: SpaceForMarshaledValue: ") + String(SpaceForMarshaledValue));
// will the item fit in the remaining space? Fragment if not
if (AvailMarshaledLength < MinimumMarshaledLength)
{
// Serial.println(String("MarshalControl: Cannot Marshal control. Not enough space for basic headers."));
EstimatedMarshaledLength = 0;
return false;
}
uint32_t MaxValueLength = (SpaceForMarshaledValue / JsonMarshalingRatio);
// Serial.println(String("MarshalControl: MaxValueLength: ") + String(MaxValueLength));
uint32_t ValueLenToSend = min((value.length() - StartingOffset), MaxValueLength);
// Serial.println(String("MarshalControl: ValueLenToSend: ") + String(ValueLenToSend));
uint32_t AdjustedMarshaledLength = (ValueLenToSend * JsonMarshalingRatio) + MinimumMarshaledLength;
// Serial.println(String("MarshalControl: AdjustedMarshaledLength: ") + String(AdjustedMarshaledLength));
bool NeedToFragment = (ValueLenToSend < value.length());
// Serial.println(String("MarshalControl: NeedToFragment: ") + String(NeedToFragment));
if ((AdjustedMarshaledLength > AvailMarshaledLength) && (0 != ValueLenToSend))
{
// Serial.println(String("MarshalControl: Cannot Marshal control. Not enough space for marshaled control."));
EstimatedMarshaledLength = 0;
return false;
}
EstimatedMarshaledLength = AdjustedMarshaledLength;
// are we fragmenting?
if(NeedToFragment || StartingOffset)
{
// Serial.println(String("MarshalControl:Start Fragment Processing"));
// Serial.println(String("MarshalControl:id: ") + String(id));
// Serial.println(String("MarshalControl:StartingOffset: ") + String(StartingOffset));
/*
if(0 == StartingOffset) if(0 == StartingOffset)
{ {
Serial.println(String("MarshalControl: New control to fragement. ID: ") + String(id)); Serial.println(String("MarshalControl: New control to fragement. ID: ") + String(id));
@ -73,16 +121,17 @@ void Control::MarshalControl(JsonObject & _item, bool refresh, uint32_t Starting
Serial.println(String("MarshalControl: Next fragement. ID: ") + String(id)); Serial.println(String("MarshalControl: Next fragement. ID: ") + String(id));
} }
*/ */
// indicate that no additional controls should be sent // indicate that no additional controls should be sent
ControlIsFragmented = true;
// fill in the fragment header
_item[F("type")] = uint32_t(ControlType::Fragment); _item[F("type")] = uint32_t(ControlType::Fragment);
_item[F("id")] = id; _item[F("id")] = id;
length = min((length - StartingOffset), maxLength);
// Serial.println(String("MarshalControl:Final length: ") + String(length)); // Serial.println(String("MarshalControl:Final length: ") + String(length));
_item[F("offset")] = StartingOffset; _item[F("offset")] = StartingOffset;
_item[F("length")] = length; _item[F("length")] = ValueLenToSend;
_item[F("total")] = value.length(); _item[F("total")] = value.length();
item = _item.createNestedObject(F("control")); item = _item.createNestedObject(F("control"));
} }
@ -99,7 +148,7 @@ void Control::MarshalControl(JsonObject & _item, bool refresh, uint32_t Starting
} }
item[F("label")] = label; item[F("label")] = label;
item[F ("value")] = (ControlType::Password == type) ? F ("--------") : value.substring(StartingOffset, length + StartingOffset); item[F ("value")] = (ControlType::Password == type) ? F ("--------") : value.substring(StartingOffset, StartingOffset + ValueLenToSend);
item[F("visible")] = visible; item[F("visible")] = visible;
item[F("color")] = (int)color; item[F("color")] = (int)color;
item[F("enabled")] = enabled; item[F("enabled")] = enabled;
@ -132,6 +181,9 @@ void Control::MarshalControl(JsonObject & _item, bool refresh, uint32_t Starting
item[F("selected")] = ""; item[F("selected")] = "";
} }
} }
// Serial.println(String("MarshalControl:Done"));
return ControlIsFragmented;
} }
void Control::MarshalErrorMessage(JsonObject & item) void Control::MarshalErrorMessage(JsonObject & item)
@ -282,10 +334,3 @@ void Control::onWsEvent(String & cmd, String& data)
} }
} while (false); } while (false);
} }
void Control::EstimateMarshaledSize()
{
EstimatedMarshaledSize = MarshalingOverhead + (JsonMarshalingRatio * (strlen(label) + value.length()));
} // EstimateSerializedSize

View File

@ -83,26 +83,23 @@ public:
void SendCallback(int type); void SendCallback(int type);
bool HasCallback() { return (nullptr != callback); } bool HasCallback() { return (nullptr != callback); }
void MarshalControl(ArduinoJson::JsonObject& item, bool refresh, uint32_t DataOffset); bool MarshalControl(ArduinoJson::JsonObject& item, bool refresh, uint32_t DataOffset, uint32_t MaxLength, uint32_t & EstimmatedUsedSpace);
void MarshalErrorMessage(ArduinoJson::JsonObject& item); void MarshalErrorMessage(ArduinoJson::JsonObject& item);
void DeleteControl(); void DeleteControl();
void onWsEvent(String& cmd, String& data); void onWsEvent(String& cmd, String& data);
inline bool ToBeDeleted() { return _ToBeDeleted; } inline bool ToBeDeleted() { return _ToBeDeleted; }
inline bool NeedsSync(uint32_t lastControlChangeID) {return (false == _ToBeDeleted) && (lastControlChangeID < ControlChangeID);} inline bool NeedsSync(uint32_t lastControlChangeID) {return (false == _ToBeDeleted) && (lastControlChangeID < ControlChangeID);}
void SetControlChangedId(uint32_t value) {ControlChangeID = value; EstimateMarshaledSize();} void SetControlChangedId(uint32_t value) {ControlChangeID = value;}
uint32_t GetEstimatedMarshaledSize() {return EstimatedMarshaledSize;}
private: private:
bool _ToBeDeleted = false; bool _ToBeDeleted = false;
uint32_t ControlChangeID = 0; uint32_t ControlChangeID = 0;
uint32_t EstimatedMarshaledSize = 0;
String OldValue = emptyString; String OldValue = emptyString;
// multiplier for converting a typical controller label and value to a Json object // multiplier for converting a typical controller label or value to a Json object
#define JsonMarshalingRatio 6 #define JsonMarshalingRatio 3
// estimated number of bytes for the fixed portion of a control rendered as Json // Marshaed Control overhead length
#define MarshalingOverhead 90 #define JsonMarshaledOverhead 64
void EstimateMarshaledSize();
}; };
#define UI_TITLE ControlType::Title #define UI_TITLE ControlType::Title