Optimize hot UI accessors and CSS length parsing:

Inline frequently used scene and UI state accessors to reduce function
  call overhead during layout, rendering, and rich-text traversal.

  Add string-view numeric conversion support and parse CSS length values
  without temporary number or unit strings. Preserve existing string
  overloads for compatibility and keep CSS function parsing unchanged.

  Add focused parsing coverage and benchmarks. Full-suite profiling showed
  substantial reductions in layout invalidation, whitespace traversal, and
  CSS length parsing costs.
This commit is contained in:
Martín Lucas Golini
2026-07-11 00:10:56 -03:00
parent ebae37b462
commit f6bce5d044
21 changed files with 344 additions and 321 deletions
@@ -10,6 +10,21 @@ Session analyzed:
/home/downloads/2026-07-09_19-52-19_eepp-unit_tests.slp
```
Current baseline capture after CSS selector phases 1-8:
```text
/tmp/eepp-unit-tests-current-final-2026-07-10.linux
```
Accessor-inlining comparison capture:
```text
/tmp/eepp-unit-tests-inline-accessors-2026-07-10.linux
```
Both current captures ran all 737 release tests at 500 Hz. The post-change run passed 736 tests
with one skipped test.
MCP could not open the packaged `.slp` directly, so it was extracted to `/tmp/superluminal-eepp-unit-tests` and the contained `.session` was queried.
## Capture Notes
@@ -108,6 +123,33 @@ Validation:
### 3. Fast-parse common CSS lengths without allocations
**Status: Implemented and measured**
`StyleSheetLength::fromString()` now trims once, scans scalar numeric prefixes directly from a
`std::string_view`, and converts the numeric subview through `String::fromString()`. The core string
API exposes `std::string_view` numeric overloads while retaining exact `std::string` forwarding
overloads for source and ABI compatibility with the implicitly constructible `EE::String` type.
The CSS parser no longer builds separate number/unit strings or retries parsing by removing
characters. Function expressions retain the existing parser, and position keywords map directly
to percentage lengths without recursive string construction.
Validation includes signed values, leading-dot decimals, scientific notation, surrounding
whitespace, position keywords, unitless and unknown units, `pxAsDp`, all existing CSS function
tests, and a dedicated release benchmark.
- Final focused benchmark median for 320,000 mixed values: 13.67 ms to 10.10 ms, a 26.1%
reduction.
- Before numeric conversion was centralized in `String`, the full-suite capture measured 105.4 ms
inclusive / 44.6 ms exclusive to 46.5 ms / 12.4 ms. A future full capture should refresh those
aggregate numbers for the final implementation.
- Post-change full release suite: 737 passed, one skipped.
Comparison capture:
```text
/tmp/eepp-unit-tests-length-fast-path-2026-07-10.linux
```
Files:
```text
@@ -305,6 +347,25 @@ Validation:
### 10. Broaden hot accessor/type-check inlining beyond selector code
**Status: Core hierarchy implemented and measured**
The trivial `getType()` / `isType()` implementations for `Node`, `UINode`, `UIWidget`,
`UILayout`, `UIHTMLWidget`, and `UIRichText` are now inline, along with `Node::isLayout()`.
In the comparable full-suite captures:
- `UISceneNode::invalidateLayout()` decreased from 86.1 ms inclusive / 41.5 ms exclusive to
26.3 ms total.
- Base type accessor symbols largely disappeared from the hot function list.
- `getEffectiveWhiteSpaceCollapse()` decreased from 344.0 ms to 162.9 ms inclusive, partly
because its repeated type checks became cheaper.
- Unit-test process CPU time decreased from 20.12 s to 19.88 s, approximately 1.2%. Treat this
whole-process result as directional because the full suite contains rendering and image-diff
noise.
Further subclass inlining remains possible, especially table widgets, but the next independent
high-value target is CSS length parsing.
Files:
```text
+13
View File
@@ -7,6 +7,7 @@
#include <cstring>
#include <functional>
#include <string>
#include <string_view>
#include <uchar.h>
#include <vector>
@@ -539,6 +540,18 @@ class EE_API String {
static bool fromString( float& t, const std::string& s );
static bool fromString( double& t, const std::string& s );
/** Converts from a string view to type */
static bool fromString( Int8& t, std::string_view s, int base = 10 );
static bool fromString( Int16& t, std::string_view s, int base = 10 );
static bool fromString( Int32& t, std::string_view s, int base = 10 );
static bool fromString( Int64& t, std::string_view s, int base = 10 );
static bool fromString( Uint8& t, std::string_view s, int base = 10 );
static bool fromString( Uint16& t, std::string_view s, int base = 10 );
static bool fromString( Uint32& t, std::string_view s, int base = 10 );
static bool fromString( Uint64& t, std::string_view s, int base = 10 );
static bool fromString( float& t, std::string_view s );
static bool fromString( double& t, std::string_view s );
/** Converts from a String to type */
static bool fromString( Int8& t, const String& s, int base = 10 );
static bool fromString( Int16& t, const String& s, int base = 10 );
+35 -29
View File
@@ -202,7 +202,7 @@ class EE_API Node : public Transformable {
*
* @return The node type as a Uint32.
*/
virtual Uint32 getType() const;
virtual Uint32 getType() const { return 0; }
/**
* @brief Checks if the node is of a specific type.
@@ -212,7 +212,7 @@ class EE_API Node : public Transformable {
* @param type The type identifier to check.
* @return True if the node is of the specified type, false otherwise.
*/
virtual bool isType( const Uint32& type ) const;
virtual bool isType( const Uint32& type ) const { return Node::getType() == type; }
/** @return True if this node is a UITextNode, false otherwise. */
inline bool isTextNode() const { return 0 != ( mNodeFlags & NODE_FLAG_TEXTNODE ); }
@@ -279,7 +279,7 @@ class EE_API Node : public Transformable {
*
* @return The size as a Sizef.
*/
virtual const Sizef& getSize() const;
virtual const Sizef& getSize() const { return mSize; }
/**
* @brief Gets the node size in actual screen pixels.
@@ -289,7 +289,7 @@ class EE_API Node : public Transformable {
*
* @return The pixel size as a Sizef.
*/
const Sizef& getPixelsSize() const;
inline const Sizef& getPixelsSize() const { return mSize; }
/**
* @brief Sets the visibility of the node.
@@ -322,7 +322,7 @@ class EE_API Node : public Transformable {
*
* @return True if the node is visible, false otherwise.
*/
bool isVisible() const;
inline bool isVisible() const { return mVisible; }
/**
* @brief Checks if the node and all its parents are visible.
@@ -350,7 +350,7 @@ class EE_API Node : public Transformable {
*
* @return True if the node is enabled, false if disabled.
*/
bool isEnabled() const;
inline bool isEnabled() const { return mEnabled; }
/**
* @brief Checks if the node is disabled.
@@ -359,7 +359,7 @@ class EE_API Node : public Transformable {
*
* @return True if the node is disabled, false otherwise.
*/
bool isDisabled() const;
inline bool isDisabled() const { return !mEnabled; }
/**
* @brief Gets the parent node.
@@ -468,7 +468,7 @@ class EE_API Node : public Transformable {
*
* @return The stored user data pointer.
*/
const UintPtr& getData() const;
inline const UintPtr& getData() const { return mData; }
/**
* @brief Sets the blend mode for this node.
@@ -544,7 +544,7 @@ class EE_API Node : public Transformable {
*
* @return True if this node is a SceneNode, false otherwise.
*/
bool isSceneNode() const;
inline bool isSceneNode() const { return 0 != ( mNodeFlags & NODE_FLAG_SCENENODE ); }
/**
* @brief Checks if this node is a UISceneNode.
@@ -553,7 +553,7 @@ class EE_API Node : public Transformable {
*
* @return True if this node is a UISceneNode, false otherwise.
*/
bool isUISceneNode() const;
inline bool isUISceneNode() const { return 0 != ( mNodeFlags & NODE_FLAG_UISCENENODE ); }
/**
* @brief Checks if this node is a UINode.
@@ -562,7 +562,7 @@ class EE_API Node : public Transformable {
*
* @return True if this node is a UINode, false otherwise.
*/
bool isUINode() const;
inline bool isUINode() const { return 0 != ( mNodeFlags & NODE_FLAG_UINODE ); }
/**
* @brief Checks if this node is a UIWidget.
@@ -580,7 +580,7 @@ class EE_API Node : public Transformable {
*
* @return True if this node is a Window, false otherwise.
*/
bool isWindow() const;
inline bool isWindow() const { return 0 != ( mNodeFlags & NODE_FLAG_WINDOW ); }
/**
* @brief Checks if this node is a Layout.
@@ -590,7 +590,7 @@ class EE_API Node : public Transformable {
*
* @return True if this node is a Layout, false otherwise.
*/
bool isLayout() const;
inline bool isLayout() const { return 0 != ( mNodeFlags & NODE_FLAG_LAYOUT ); }
/**
* @brief Checks if clipping is enabled for this node.
@@ -599,21 +599,21 @@ class EE_API Node : public Transformable {
*
* @return True if clipping is enabled, false otherwise.
*/
bool isClipped() const;
inline bool isClipped() const { return 0 != ( mNodeFlags & NODE_FLAG_CLIP_ENABLE ); }
/**
* @brief Checks if this node has rotation applied.
*
* @return True if the node's rotation is non-zero, false otherwise.
*/
bool isRotated() const;
inline bool isRotated() const { return 0 != ( mNodeFlags & NODE_FLAG_ROTATED ); }
/**
* @brief Checks if this node has scaling applied.
*
* @return True if the node's scale is not (1,1), false otherwise.
*/
bool isScaled() const;
inline bool isScaled() const { return 0 != ( mNodeFlags & NODE_FLAG_SCALED ); }
/**
* @brief Checks if this node uses a frame buffer.
@@ -622,21 +622,23 @@ class EE_API Node : public Transformable {
*
* @return True if using frame buffer rendering, false otherwise.
*/
bool isFrameBuffer() const;
inline bool isFrameBuffer() const { return 0 != ( mNodeFlags & NODE_FLAG_FRAME_BUFFER ); }
/**
* @brief Checks if the mouse is currently over this node.
*
* @return True if the mouse cursor is over this node, false otherwise.
*/
bool isMouseOver() const;
inline bool isMouseOver() const { return 0 != ( mNodeFlags & NODE_FLAG_MOUSEOVER ); }
/**
* @brief Checks if the mouse is over this node or any of its children.
*
* @return True if the mouse is over this node or any descendant, false otherwise.
*/
bool isMouseOverMeOrChildren() const;
inline bool isMouseOverMeOrChildren() const {
return 0 != ( mNodeFlags & NODE_FLAG_MOUSEOVER_ME_OR_CHILD );
}
/**
* @brief Checks if this node and all its parents are visible in the tree.
@@ -756,14 +758,14 @@ class EE_API Node : public Transformable {
*
* @return Pointer to the first child or nullptr if no children exist.
*/
Node* getFirstChild() const;
inline Node* getFirstChild() const { return mChild; }
/**
* @brief Gets the last child node.
*
* @return Pointer to the last child or nullptr if no children exist.
*/
Node* getLastChild() const;
inline Node* getLastChild() const { return mChildLast; }
/**
* @brief Gets the world polygon of this node.
@@ -873,7 +875,7 @@ class EE_API Node : public Transformable {
*
* @return The ID hash value.
*/
const String::HashType& getIdHash() const;
inline const String::HashType& getIdHash() const { return mIdHash; }
/**
* @brief Finds a descendant node by its ID string.
@@ -1030,7 +1032,7 @@ class EE_API Node : public Transformable {
*
* @return True if reverse drawing is enabled, false otherwise.
*/
bool isReverseDraw() const;
inline bool isReverseDraw() const { return 0 != ( mNodeFlags & NODE_FLAG_REVERSE_DRAW ); }
/**
* @brief Enables or disables reverse drawing order.
@@ -1428,7 +1430,7 @@ class EE_API Node : public Transformable {
*
* @return True if this node has focus, false otherwise.
*/
bool hasFocus() const;
inline bool hasFocus() const { return 0 != ( mNodeFlags & NODE_FLAG_HAS_FOCUS ); }
/**
* @brief Checks if this node or any descendant has focus.
@@ -1553,7 +1555,7 @@ class EE_API Node : public Transformable {
*
* @return Pointer to the SceneNode or nullptr if not found.
*/
SceneNode* getSceneNode() const;
inline SceneNode* getSceneNode() const { return mSceneNode; }
/**
* @brief Gets the event dispatcher associated with this node.
@@ -1764,7 +1766,7 @@ class EE_API Node : public Transformable {
*
* @return True if the node's loading flag is set, false otherwise.
*/
bool isLoadingState() const;
inline bool isLoadingState() const { return 0 != ( mNodeFlags & NODE_FLAG_LOADING ); }
/**
* @brief Called when the node's ID changes.
@@ -1779,7 +1781,7 @@ class EE_API Node : public Transformable {
*
* @return True if the close flag is set (node will be removed), false otherwise.
*/
bool isClosing() const;
inline bool isClosing() const { return 0 != ( mNodeFlags & NODE_FLAG_CLOSE ); }
/**
* @brief Checks if the node is marked for closure or any node in its parent tree.
@@ -1793,7 +1795,9 @@ class EE_API Node : public Transformable {
*
* @return True if the closing children flag is set, false otherwise.
*/
bool isClosingChildren() const;
inline bool isClosingChildren() const {
return 0 != ( mNodeFlags & NODE_FLAG_CLOSING_CHILDREN );
}
/**
* @brief Finds the node under a point, considering hit testing.
@@ -2554,7 +2558,9 @@ class EE_API Node : public Transformable {
*
* @return True if NODE_FLAG_SCHEDULED_UPDATE is set.
*/
bool isSubscribedForScheduledUpdate();
inline bool isSubscribedForScheduledUpdate() {
return 0 != ( mNodeFlags & NODE_FLAG_SCHEDULED_UPDATE );
}
};
}} // namespace EE::Scene
+13 -5
View File
@@ -53,9 +53,11 @@ class EE_API UIHTMLWidget : public UILayout {
virtual ~UIHTMLWidget();
virtual Uint32 getType() const;
virtual Uint32 getType() const { return UI_TYPE_HTML_WIDGET; }
virtual bool isType( const Uint32& type ) const;
virtual bool isType( const Uint32& type ) const {
return UIHTMLWidget::getType() == type || UILayout::isType( type );
}
UILayouter* getLayouter();
@@ -67,9 +69,13 @@ class EE_API UIHTMLWidget : public UILayout {
void setDisplay( CSSDisplay display );
bool isFlex() const;
inline bool isFlex() const {
return mDisplay == CSSDisplay::Flex || mDisplay == CSSDisplay::InlineFlex;
}
bool isGrid() const;
inline bool isGrid() const {
return mDisplay == CSSDisplay::Grid || mDisplay == CSSDisplay::InlineGrid;
}
CSSPosition getCSSPosition() const { return mPosition; }
@@ -317,7 +323,9 @@ class EE_API UIHTMLWidget : public UILayout {
virtual void invalidateIntrinsicSize();
bool isOutOfFlow() const;
inline bool isOutOfFlow() const {
return mPosition == CSSPosition::Absolute || mPosition == CSSPosition::Fixed;
}
bool establishesBlockFormattingContext() const;
+4 -2
View File
@@ -18,9 +18,11 @@ class EE_API UILayout : public UIWidget {
Uint64 treeUpdates{ 0 };
};
virtual Uint32 getType() const;
virtual Uint32 getType() const { return UI_TYPE_LAYOUT; }
virtual bool isType( const Uint32& type ) const;
virtual bool isType( const Uint32& type ) const {
return UILayout::getType() == type || UIWidget::isType( type );
}
virtual const Sizef& getSize() const;
+4 -2
View File
@@ -117,7 +117,7 @@ class EE_API UINode : public Node {
*
* @return The node type as a Uint32.
*/
virtual Uint32 getType() const;
virtual Uint32 getType() const { return UI_TYPE_UINODE; }
/**
* @brief Checks if the node is of a specific type.
@@ -127,7 +127,9 @@ class EE_API UINode : public Node {
* @param type The type identifier to check.
* @return True if the node is of the specified type, false otherwise.
*/
virtual bool isType( const Uint32& type ) const;
virtual bool isType( const Uint32& type ) const {
return UINode::getType() == type || Node::isType( type );
}
/**
* @brief Sets the node position in density-independent pixels (dp).
+4 -2
View File
@@ -68,9 +68,11 @@ class EE_API UIRichText : public UIHTMLWidget {
static UIRichText* NewBlockquote() { return UIRichText::NewWithTag( "blockquote" ); };
virtual Uint32 getType() const;
virtual Uint32 getType() const { return UI_TYPE_RICHTEXT; }
virtual bool isType( const Uint32& type ) const;
virtual bool isType( const Uint32& type ) const {
return UIRichText::getType() == type || UIHTMLWidget::isType( type );
}
virtual void draw();
+4 -2
View File
@@ -80,7 +80,7 @@ class EE_API UIWidget : public UINode {
*
* @return The widget type as a Uint32.
*/
virtual Uint32 getType() const;
virtual Uint32 getType() const { return UI_TYPE_WIDGET; }
/**
* @brief Checks if the widget is of a specific type.
@@ -90,7 +90,9 @@ class EE_API UIWidget : public UINode {
* @param type The type identifier to check.
* @return True if the widget is of the specified type, false otherwise.
*/
virtual bool isType( const Uint32& type ) const;
virtual bool isType( const Uint32& type ) const {
return UIWidget::getType() == type || UINode::isType( type );
}
/**
* @brief Sets multiple flags on the widget.
@@ -7,6 +7,7 @@
#include <eepp/system/clock.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/sys.hpp>
#include <eepp/ui/css/stylesheetlength.hpp>
#include <eepp/ui/css/stylesheetparser.hpp>
#include <eepp/ui/css/stylesheetselector.hpp>
#include <eepp/ui/uihtmlwidget.hpp>
@@ -199,6 +200,28 @@ UTEST( Benchmark, CSSAttributeSelectorMatching ) {
Engine::destroySingleton();
}
UTEST( Benchmark, CSSLengthParsing ) {
const std::array<std::string, 16> values = {
"0", "12px", "-1.5em", "+24dp", "100%", ".75rem", "50vw", "25vh",
"2.54cm", "12pt", "1e3px", "center", "left", "10vmin", "4ch", "8dpr" };
const int iterations = getSelectorMatchingIterations();
Float parsedValueSum = 0;
Clock parsingClock;
for ( int iteration = 0; iteration < iterations; ++iteration ) {
for ( const auto& value : values ) {
const auto length = StyleSheetLength::fromString( value );
parsedValueSum += length.getValue();
}
}
const Time parsingElapsed = parsingClock.getElapsedTime();
EXPECT_TRUE( parsedValueSum > 0 );
UTEST_PRINT_INFO(
String::format( "CSS length parsing: %lld us", parsingElapsed.asMicroseconds() ).c_str() );
UTEST_PRINT_INFO(
String::format( "CSS length values: %d", iterations * values.size() ).c_str() );
}
static int getMarkdownFlushIterations() {
if ( const char* env = std::getenv( "EE_MARKDOWN_BENCH_FLUSH_ITERATIONS" ) ) {
Int32 val = markdownFlushIterations;
+60 -20
View File
@@ -54,7 +54,7 @@
namespace EE {
template <typename T> static bool _fromString( T& t, const std::string& s, int base = 10 ) {
template <typename T> static bool _fromString( T& t, std::string_view s, int base = 10 ) {
const char* begin = s.data();
const char* end = s.data() + s.size();
@@ -109,83 +109,123 @@ template <class T> static std::string _toString( const T& value, size_t digitsAf
}
bool String::fromString( Int8& t, const std::string& s, int base ) {
return _fromString<>( t, s, base );
return fromString( t, std::string_view( s ), base );
}
bool String::fromString( Int16& t, const std::string& s, int base ) {
return _fromString<>( t, s, base );
return fromString( t, std::string_view( s ), base );
}
bool String::fromString( Int32& t, const std::string& s, int base ) {
return _fromString<>( t, s, base );
return fromString( t, std::string_view( s ), base );
}
bool String::fromString( Int64& t, const std::string& s, int base ) {
return _fromString<>( t, s, base );
return fromString( t, std::string_view( s ), base );
}
bool String::fromString( Uint8& t, const std::string& s, int base ) {
return _fromString<>( t, s, base );
return fromString( t, std::string_view( s ), base );
}
bool String::fromString( Uint16& t, const std::string& s, int base ) {
return _fromString<>( t, s, base );
return fromString( t, std::string_view( s ), base );
}
bool String::fromString( Uint32& t, const std::string& s, int base ) {
return _fromString<>( t, s, base );
return fromString( t, std::string_view( s ), base );
}
bool String::fromString( Uint64& t, const std::string& s, int base ) {
return _fromString<>( t, s, base );
return fromString( t, std::string_view( s ), base );
}
bool String::fromString( float& t, const std::string& s ) {
return _fromString<>( t, s );
return fromString( t, std::string_view( s ) );
}
bool String::fromString( double& t, const std::string& s ) {
return fromString( t, std::string_view( s ) );
}
bool String::fromString( Int8& t, std::string_view s, int base ) {
return _fromString<>( t, s, base );
}
bool String::fromString( Int16& t, std::string_view s, int base ) {
return _fromString<>( t, s, base );
}
bool String::fromString( Int32& t, std::string_view s, int base ) {
return _fromString<>( t, s, base );
}
bool String::fromString( Int64& t, std::string_view s, int base ) {
return _fromString<>( t, s, base );
}
bool String::fromString( Uint8& t, std::string_view s, int base ) {
return _fromString<>( t, s, base );
}
bool String::fromString( Uint16& t, std::string_view s, int base ) {
return _fromString<>( t, s, base );
}
bool String::fromString( Uint32& t, std::string_view s, int base ) {
return _fromString<>( t, s, base );
}
bool String::fromString( Uint64& t, std::string_view s, int base ) {
return _fromString<>( t, s, base );
}
bool String::fromString( float& t, std::string_view s ) {
return _fromString<>( t, s );
}
bool String::fromString( double& t, std::string_view s ) {
return _fromString<>( t, s );
}
bool String::fromString( Int8& t, const String& s, int base ) {
return _fromString<>( t, s, base );
return _fromString<>( t, s.toUtf8(), base );
}
bool String::fromString( Int16& t, const String& s, int base ) {
return _fromString<>( t, s, base );
return _fromString<>( t, s.toUtf8(), base );
}
bool String::fromString( Int32& t, const String& s, int base ) {
return _fromString<>( t, s, base );
return _fromString<>( t, s.toUtf8(), base );
}
bool String::fromString( Int64& t, const String& s, int base ) {
return _fromString<>( t, s, base );
return _fromString<>( t, s.toUtf8(), base );
}
bool String::fromString( Uint8& t, const String& s, int base ) {
return _fromString<>( t, s, base );
return _fromString<>( t, s.toUtf8(), base );
}
bool String::fromString( Uint16& t, const String& s, int base ) {
return _fromString<>( t, s, base );
return _fromString<>( t, s.toUtf8(), base );
}
bool String::fromString( Uint32& t, const String& s, int base ) {
return _fromString<>( t, s, base );
return _fromString<>( t, s.toUtf8(), base );
}
bool String::fromString( Uint64& t, const String& s, int base ) {
return _fromString<>( t, s, base );
return _fromString<>( t, s.toUtf8(), base );
}
bool String::fromString( float& t, const String& s ) {
return _fromString<>( t, s );
return _fromString<>( t, s.toUtf8() );
}
bool String::fromString( double& t, const String& s ) {
return _fromString<>( t, s );
return _fromString<>( t, s.toUtf8() );
}
std::string String::toString( const Int8& i ) {
-116
View File
@@ -96,14 +96,6 @@ void Node::nodeToWorldTranslation( Vector2f& Pos ) const {
}
}
Uint32 Node::getType() const {
return 0;
}
bool Node::isType( const Uint32& type ) const {
return Node::getType() == type;
}
void Node::messagePost( const NodeMessage* Msg ) {
Node* node = this;
while ( NULL != node ) {
@@ -164,14 +156,6 @@ void Node::setInternalHeight( const Float& height ) {
setInternalSize( Sizef( getSize().getWidth(), height ) );
}
const Sizef& Node::getSize() const {
return mSize;
}
const Sizef& Node::getPixelsSize() const {
return mSize;
}
Node* Node::setVisible( const bool& visible, bool emitEventNotification ) {
if ( mVisible != visible ) {
mVisible = visible;
@@ -197,10 +181,6 @@ Node* Node::setChildrenVisibility( bool visible, bool emitEventNotification ) {
return this;
}
bool Node::isVisible() const {
return mVisible;
}
bool Node::hasVisibility() const {
const Node* cur = this;
while ( cur ) {
@@ -219,14 +199,6 @@ Node* Node::setEnabled( const bool& enabled ) {
return this;
}
bool Node::isEnabled() const {
return mEnabled;
}
bool Node::isDisabled() const {
return !mEnabled;
}
void Node::updateDrawInvalidator( bool force ) {
mNodeDrawInvalidator = getDrawInvalidator();
@@ -253,10 +225,6 @@ void Node::unsubscribeScheduledUpdate() {
}
}
bool Node::isSubscribedForScheduledUpdate() {
return 0 != ( mNodeFlags & NODE_FLAG_SCHEDULED_UPDATE );
}
Node* Node::setParent( Node* parent ) {
eeASSERT( NULL != parent );
@@ -370,14 +338,6 @@ Uint32 Node::onMouseClick( const Vector2i& Pos, const Uint32& Flags ) {
return 1;
}
bool Node::isMouseOver() const {
return 0 != ( mNodeFlags & NODE_FLAG_MOUSEOVER );
}
bool Node::isMouseOverMeOrChildren() const {
return 0 != ( mNodeFlags & NODE_FLAG_MOUSEOVER_ME_OR_CHILD );
}
Uint32 Node::onMouseDoubleClick( const Vector2i& Pos, const Uint32& Flags ) {
sendMouseEvent( Event::MouseDoubleClick, Pos, Flags );
return 1;
@@ -442,10 +402,6 @@ Node* Node::setData( const UintPtr& data ) {
return this;
}
const UintPtr& Node::getData() const {
return mData;
}
Node* Node::setBlendMode( const BlendMode& blend ) {
mBlend = blend;
invalidateDraw();
@@ -755,10 +711,6 @@ void Node::onIdChange() {
sendCommonEvent( Event::OnIdChange );
}
bool Node::isClosing() const {
return 0 != ( mNodeFlags & NODE_FLAG_CLOSE );
}
bool Node::inClosingTree() const {
if ( isClosing() )
return true;
@@ -771,14 +723,6 @@ bool Node::inClosingTree() const {
return false;
}
bool Node::isClosingChildren() const {
return 0 != ( mNodeFlags & NODE_FLAG_CLOSING_CHILDREN );
}
const String::HashType& Node::getIdHash() const {
return mIdHash;
}
static bool isNestedSceneBoundary( const Node* child ) {
return child->isSceneNode() && child->getSceneNode() == child;
}
@@ -933,10 +877,6 @@ void Node::setLoadingState( bool loading ) {
writeNodeFlag( NODE_FLAG_LOADING, loading ? 1 : 0 );
}
bool Node::isLoadingState() const {
return 0 != ( mNodeFlags & NODE_FLAG_LOADING );
}
Uint32 Node::getChildCount() const {
Node* child = mChild;
Uint32 count = 0;
@@ -1002,14 +942,6 @@ Uint32 Node::getNodeOfTypeIndex() const {
return nodeIndex;
}
Node* Node::getFirstChild() const {
return mChild;
}
Node* Node::getLastChild() const {
return mChildLast;
}
Node* Node::overFind( const Vector2f& point ) {
Node* pOver = NULL;
@@ -1078,42 +1010,6 @@ void Node::onSceneChange() {
}
}
bool Node::isWindow() const {
return 0 != ( mNodeFlags & NODE_FLAG_WINDOW );
}
bool Node::isLayout() const {
return 0 != ( mNodeFlags & NODE_FLAG_LAYOUT );
}
bool Node::isClipped() const {
return 0 != ( mNodeFlags & NODE_FLAG_CLIP_ENABLE );
}
bool Node::isRotated() const {
return 0 != ( mNodeFlags & NODE_FLAG_ROTATED );
}
bool Node::isScaled() const {
return 0 != ( mNodeFlags & NODE_FLAG_SCALED );
}
bool Node::isFrameBuffer() const {
return 0 != ( mNodeFlags & NODE_FLAG_FRAME_BUFFER );
}
bool Node::isSceneNode() const {
return 0 != ( mNodeFlags & NODE_FLAG_SCENENODE );
}
bool Node::isUISceneNode() const {
return 0 != ( mNodeFlags & NODE_FLAG_UISCENENODE );
}
bool Node::isUINode() const {
return 0 != ( mNodeFlags & NODE_FLAG_UINODE );
}
bool Node::isMeOrParentTreeVisible() const {
const Node* node = this;
while ( NULL != node ) {
@@ -1342,10 +1238,6 @@ void Node::nodeToWorld( Vector2f& pos ) const {
pos = Vector2f( toPos.x, toPos.y );
}
bool Node::isReverseDraw() const {
return 0 != ( mNodeFlags & NODE_FLAG_REVERSE_DRAW );
}
void Node::setReverseDraw( bool reverseDraw ) {
writeNodeFlag( NODE_FLAG_REVERSE_DRAW, reverseDraw ? 1 : 0 );
invalidateDraw();
@@ -1357,10 +1249,6 @@ void Node::invalidateDraw() {
}
}
SceneNode* Node::getSceneNode() const {
return mSceneNode;
}
SceneNode* Node::findSceneNode() {
Node* node = mParentNode;
while ( node != NULL ) {
@@ -1747,10 +1635,6 @@ Uint32 Node::onFocusLoss() {
return 1;
}
bool Node::hasFocus() const {
return 0 != ( mNodeFlags & NODE_FLAG_HAS_FOCUS );
}
bool Node::hasFocusWithin() const {
return hasFocus() || inParentTreeOf( getEventDispatcher()->getFocusNode() );
}
+72 -74
View File
@@ -44,22 +44,6 @@ enum PercentagePositions : String::HashType {
None = 0,
};
static std::string positionToPercentage( const PercentagePositions& pos ) {
switch ( pos ) {
case Center:
return "50%";
case Left:
case Top:
return "0%";
case Right:
case Bottom:
return "100%";
default:
case None:
return "";
}
}
static PercentagePositions isPercentagePosition( const String::HashType& strHash ) {
switch ( strHash ) {
case PercentagePositions::Center:
@@ -76,6 +60,45 @@ static PercentagePositions isPercentagePosition( const String::HashType& strHash
return PercentagePositions::None;
}
static size_t numericPrefixLength( std::string_view value ) {
if ( value.empty() )
return 0;
size_t pos = 0;
if ( value[pos] == '-' || value[pos] == '+' )
pos++;
bool hasDigit = false;
bool hasDot = false;
while ( pos < value.size() ) {
const char c = value[pos];
if ( c >= '0' && c <= '9' ) {
hasDigit = true;
pos++;
} else if ( c == '.' && !hasDot ) {
hasDot = true;
pos++;
} else {
break;
}
}
if ( !hasDigit )
return 0;
if ( pos < value.size() && ( value[pos] == 'e' || value[pos] == 'E' ) ) {
size_t expPos = pos + 1;
if ( expPos < value.size() && ( value[expPos] == '-' || value[expPos] == '+' ) )
expPos++;
const size_t expDigits = expPos;
while ( expPos < value.size() && value[expPos] >= '0' && value[expPos] <= '9' )
expPos++;
if ( expPos != expDigits )
pos = expPos;
}
return pos;
}
StyleSheetLength::Unit StyleSheetLength::unitFromString( std::string_view unitStr ) {
switch ( String::hashToLower( unitStr ) ) {
case UnitHashes::Percentage:
@@ -187,41 +210,10 @@ bool StyleSheetLength::isLength( std::string_view unitStr ) {
if ( isFunctionString( unitStr ) )
return true;
size_t pos = 0;
if ( unitStr[pos] == '-' || unitStr[pos] == '+' )
pos++;
bool hasDigit = false;
bool hasDot = false;
while ( pos < unitStr.size() ) {
char c = unitStr[pos];
if ( c >= '0' && c <= '9' ) {
hasDigit = true;
pos++;
} else if ( c == '.' && !hasDot ) {
hasDot = true;
pos++;
} else {
break;
}
}
if ( !hasDigit )
const size_t pos = numericPrefixLength( unitStr );
if ( pos == 0 )
return false;
if ( pos < unitStr.size() && ( unitStr[pos] == 'e' || unitStr[pos] == 'E' ) ) {
size_t expPos = pos + 1;
if ( expPos < unitStr.size() && ( unitStr[expPos] == '-' || unitStr[expPos] == '+' ) )
expPos++;
bool hasExpDigit = false;
while ( expPos < unitStr.size() && unitStr[expPos] >= '0' && unitStr[expPos] <= '9' ) {
hasExpDigit = true;
expPos++;
}
if ( hasExpDigit )
pos = expPos;
}
std::string_view unit = unitStr.substr( pos );
if ( unit.empty() )
return true;
@@ -390,14 +382,11 @@ StyleSheetLength& StyleSheetLength::operator=( const StyleSheetLength& val ) {
StyleSheetLength StyleSheetLength::fromString( const std::string& str, const Float& defaultValue,
bool pxAsDp ) {
PercentagePositions isPercentage = isPercentagePosition( String::hashToLower( str ) );
if ( PercentagePositions::None != isPercentage )
return fromString( positionToPercentage( isPercentage ), defaultValue );
StyleSheetLength length;
length.setValue( defaultValue, Unit::Px );
const std::string_view value = String::trim( std::string_view( str ) );
if ( isFunctionString( str ) ) {
if ( isFunctionString( value ) ) {
Unit funcUnit = Unit::Px;
Arguments args;
if ( parseFunction( str, funcUnit, args ) ) {
@@ -409,30 +398,39 @@ StyleSheetLength StyleSheetLength::fromString( const std::string& str, const Flo
return length;
}
std::string num;
std::string unit;
for ( std::size_t i = 0; i < str.size(); i++ ) {
char c = str[i];
if ( String::isNumber( c, true, true ) || ( '-' == c && i == 0 ) ||
( '+' == c && i == 0 ) ) {
num += c;
if ( !value.empty() ) {
const char first = value.front();
const bool startsNumeric =
( first >= '0' && first <= '9' ) || first == '.' || first == '-' || first == '+';
if ( startsNumeric ) {
const size_t numberLength = numericPrefixLength( value );
if ( numberLength != 0 ) {
std::string_view number = value.substr( 0, numberLength );
if ( number.front() == '+' )
number.remove_prefix( 1 );
Float parsedValue = 0;
if ( String::fromString( parsedValue, number ) )
length.setValue( parsedValue, unitFromString( value.substr( numberLength ) ) );
}
} else {
unit = str.substr( i );
break;
switch ( isPercentagePosition( String::hashToLower( value ) ) ) {
case PercentagePositions::Center:
length.setValue( 50, Unit::Percentage );
break;
case PercentagePositions::Right:
case PercentagePositions::Bottom:
length.setValue( 100, Unit::Percentage );
break;
case PercentagePositions::Left:
case PercentagePositions::Top:
length.setValue( 0, Unit::Percentage );
break;
case PercentagePositions::None:
break;
}
}
}
if ( !num.empty() ) {
Float val = 0;
while ( !num.empty() && !String::fromString( val, num ) ) {
unit = num.back() + unit;
num.pop_back();
}
if ( !num.empty() )
length.setValue( val, unitFromString( unit ) );
}
if ( pxAsDp && length.getUnit() == Unit::Px )
length.mUnit = Unit::Dp;
-6
View File
@@ -19,7 +19,6 @@ UIHTMLTable::UIHTMLTable() : UIHTMLWidget( "table" ) {
Uint32 UIHTMLTable::getType() const {
return UI_TYPE_HTML_TABLE;
}
bool UIHTMLTable::isType( const Uint32& type ) const {
return UIHTMLTable::getType() == type || UIHTMLWidget::isType( type );
}
@@ -196,7 +195,6 @@ UIHTMLTableRow::UIHTMLTableRow() : UIHTMLWidget( "tr" ) {
Uint32 UIHTMLTableRow::getType() const {
return UI_TYPE_HTML_TABLE_ROW;
}
bool UIHTMLTableRow::isType( const Uint32& type ) const {
return UIHTMLTableRow::getType() == type || UIHTMLWidget::isType( type );
}
@@ -214,7 +212,6 @@ UIHTMLTableCell::UIHTMLTableCell( const std::string& tag ) : UIRichText( tag ) {
Uint32 UIHTMLTableCell::getType() const {
return UI_TYPE_HTML_TABLE_CELL;
}
bool UIHTMLTableCell::isType( const Uint32& type ) const {
return UIHTMLTableCell::getType() == type || UIRichText::isType( type );
}
@@ -292,7 +289,6 @@ UIHTMLTableHead::UIHTMLTableHead() : UIHTMLWidget( "thead" ) {
Uint32 UIHTMLTableHead::getType() const {
return UI_TYPE_HTML_TABLE_HEAD;
}
bool UIHTMLTableHead::isType( const Uint32& type ) const {
return UIHTMLTableHead::getType() == type || UIHTMLWidget::isType( type );
}
@@ -310,7 +306,6 @@ UIHTMLTableBody::UIHTMLTableBody() : UIHTMLWidget( "tbody" ) {
Uint32 UIHTMLTableBody::getType() const {
return UI_TYPE_HTML_TABLE_BODY;
}
bool UIHTMLTableBody::isType( const Uint32& type ) const {
return UIHTMLTableBody::getType() == type || UIHTMLWidget::isType( type );
}
@@ -328,7 +323,6 @@ UIHTMLTableFooter::UIHTMLTableFooter() : UIHTMLWidget( "tfoot" ) {
Uint32 UIHTMLTableFooter::getType() const {
return UI_TYPE_HTML_TABLE_FOOTER;
}
bool UIHTMLTableFooter::isType( const Uint32& type ) const {
return UIHTMLTableFooter::getType() == type || UIHTMLWidget::isType( type );
}
-20
View File
@@ -93,14 +93,6 @@ UILayouter* UIHTMLWidget::getLayouter() {
return mLayouter;
}
Uint32 UIHTMLWidget::getType() const {
return UI_TYPE_HTML_WIDGET;
}
bool UIHTMLWidget::isType( const Uint32& type ) const {
return UIHTMLWidget::getType() == type ? true : UILayout::isType( type );
}
bool UIHTMLWidget::isPacking() const {
UILayouter* layouter = const_cast<UIHTMLWidget*>( this )->getLayouter();
if ( layouter )
@@ -157,14 +149,6 @@ void UIHTMLWidget::setDisplay( CSSDisplay display ) {
}
}
bool UIHTMLWidget::isFlex() const {
return mDisplay == CSSDisplay::Flex || mDisplay == CSSDisplay::InlineFlex;
}
bool UIHTMLWidget::isGrid() const {
return mDisplay == CSSDisplay::Grid || mDisplay == CSSDisplay::InlineGrid;
}
Float UIHTMLWidget::getBaseline() const {
if ( mLayouter ) {
if ( isFlex() ) {
@@ -1341,10 +1325,6 @@ void UIHTMLWidget::invalidateIntrinsicSize() {
UIWidget::invalidateIntrinsicSize();
}
bool UIHTMLWidget::isOutOfFlow() const {
return mPosition == CSSPosition::Absolute || mPosition == CSSPosition::Fixed;
}
bool UIHTMLWidget::establishesBlockFormattingContext() const {
if ( mFloat != CSSFloat::None || isOutOfFlow() || mDisplay == CSSDisplay::InlineBlock )
return true;
-8
View File
@@ -51,14 +51,6 @@ void UILayout::onLayoutUpdate() {
sendCommonEvent( Event::OnLayoutUpdate );
}
Uint32 UILayout::getType() const {
return UI_TYPE_LAYOUT;
}
bool UILayout::isType( const Uint32& type ) const {
return UILayout::getType() == type ? true : UIWidget::isType( type );
}
const Sizef& UILayout::getSize() const {
if ( mDirtyLayout )
const_cast<UILayout*>( this )->updateLayout();
+5 -5
View File
@@ -39,16 +39,16 @@ UILinearLayout::UILinearLayout( const std::string& tag, const UIOrientation& ori
setClipType( ClipType::ContentBox );
}
UIOrientation UILinearLayout::getOrientation() const {
return mOrientation;
}
Uint32 UILinearLayout::getType() const {
return UI_TYPE_LINEAR_LAYOUT;
}
bool UILinearLayout::isType( const Uint32& type ) const {
return UILinearLayout::getType() == type ? true : UILayout::isType( type );
}
UIOrientation UILinearLayout::getOrientation() const {
return mOrientation;
return UILinearLayout::getType() == type || UILayout::isType( type );
}
UILinearLayout* UILinearLayout::setOrientation( const UIOrientation& orientation ) {
-8
View File
@@ -89,14 +89,6 @@ void UINode::nodeToWorldTranslation( Vector2f& Pos ) const {
}
}
Uint32 UINode::getType() const {
return UI_TYPE_UINODE;
}
bool UINode::isType( const Uint32& type ) const {
return UINode::getType() == type || Node::isType( type );
}
void UINode::setInternalPosition( const Vector2f& Pos ) {
mDpPos = Pos;
Transformable::setPosition( PixelDensity::dpToPx( Pos ) );
-8
View File
@@ -434,14 +434,6 @@ UIRichText::UIRichText( const std::string& tag ) : UIHTMLWidget( tag ) {
setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent );
}
Uint32 UIRichText::getType() const {
return UI_TYPE_RICHTEXT;
}
bool UIRichText::isType( const Uint32& type ) const {
return UIRichText::getType() == type ? true : UIHTMLWidget::isType( type );
}
const RichText& UIRichText::getRichText() {
return mRichText;
}
-8
View File
@@ -105,14 +105,6 @@ UIWidget::~UIWidget() {
eeSAFE_DELETE( mTooltip );
}
Uint32 UIWidget::getType() const {
return UI_TYPE_WIDGET;
}
bool UIWidget::isType( const Uint32& type ) const {
return UIWidget::getType() == type ? true : UINode::isType( type );
}
void UIWidget::updateAnchorsDistances() {
if ( NULL != mParentNode ) {
mDistToBorder = Rect( mPosition.x, mPosition.y,
@@ -18,6 +18,18 @@ UTEST( String, countLines ) {
EXPECT_EQ( static_cast<size_t>( 3 ), String::countLines( "\n\n" ) );
}
UTEST( String, fromStringView ) {
const std::string values = "1234.5px";
Float floatValue = 0;
EXPECT_TRUE( String::fromString( floatValue, std::string_view( values ).substr( 0, 6 ) ) );
EXPECT_NEAR( 1234.5f, floatValue, 0.0001f );
EXPECT_FALSE( String::fromString( floatValue, std::string_view( values ) ) );
Int32 intValue = 0;
EXPECT_TRUE( String::fromString( intValue, std::string_view( values ).substr( 0, 4 ) ) );
EXPECT_EQ( 1234, intValue );
}
UTEST( FileSystem, fileCountLines ) {
std::string path = Sys::getTempPath() + "eepp_test_count_lines.txt";
FileSystem::fileWrite( path, "A\nB\nC" );
@@ -153,7 +165,8 @@ UTEST( String, isLatin1 ) {
EXPECT_TRUE( str255.isLatin1() );
// Complex string with Latin1 chars
String complexLatin1 = String::fromUtf8( "Héllø Wørld"sv ); // Assuming these are in Latin1 range
String complexLatin1 =
String::fromUtf8( "Héllø Wørld"sv ); // Assuming these are in Latin1 range
// Note: 'ø' is 0xF8 (248), 'é' is 0xE9 (233). Both in Latin1.
EXPECT_TRUE( complexLatin1.isLatin1() );
@@ -162,7 +175,6 @@ UTEST( String, isLatin1 ) {
// 32 chars of 255
String longLatin1( 32, (String::StringBaseType)255 );
EXPECT_TRUE( longLatin1.isLatin1() );
}
}
@@ -187,14 +199,15 @@ UTEST( String, isAsciiHighBit ) {
UTEST( String, isAsciiPatterns ) {
// Alternating
String alt;
for (int i = 0; i < 100; i++) {
alt += (i % 2 == 0) ? 'a' : (char)128;
for ( int i = 0; i < 100; i++ ) {
alt += ( i % 2 == 0 ) ? 'a' : (char)128;
}
EXPECT_FALSE( alt.isAscii() );
// Block of invalid in middle of valid
String block(100, 'a');
for(int i=40; i<60; i++) block[i] = 200;
String block( 100, 'a' );
for ( int i = 40; i < 60; i++ )
block[i] = 200;
EXPECT_FALSE( block.isAscii() );
}
@@ -1349,6 +1349,33 @@ UTEST( CSSFunctions, ClampResolvesPixels ) {
EXPECT_EQ( 50, resolved );
}
UTEST( CSSLength, ScalarParsing ) {
auto expectLength = [&]( const std::string& value, Float expectedValue,
StyleSheetLength::Unit expectedUnit ) {
const auto length = StyleSheetLength::fromString( value );
EXPECT_NEAR( expectedValue, length.getValue(), 0.0001f );
EXPECT_EQ( expectedUnit, length.getUnit() );
};
expectLength( "12px", 12, StyleSheetLength::Unit::Px );
expectLength( "-1.5em", -1.5f, StyleSheetLength::Unit::Em );
expectLength( "+24dp", 24, StyleSheetLength::Unit::Dp );
expectLength( ".75rem", 0.75f, StyleSheetLength::Unit::Rem );
expectLength( "1e3px", 1000, StyleSheetLength::Unit::Px );
expectLength( " 50% ", 50, StyleSheetLength::Unit::Percentage );
expectLength( " center ", 50, StyleSheetLength::Unit::Percentage );
expectLength( "left", 0, StyleSheetLength::Unit::Percentage );
expectLength( "bottom", 100, StyleSheetLength::Unit::Percentage );
expectLength( "10", 10, StyleSheetLength::Unit::Dp );
expectLength( "10unknown", 10, StyleSheetLength::Unit::Dp );
const auto invalid = StyleSheetLength::fromString( "invalid", 7 );
EXPECT_EQ( 7, invalid.getValue() );
EXPECT_EQ( StyleSheetLength::Unit::Px, invalid.getUnit() );
const auto pxAsDp = StyleSheetLength::fromString( "12px", 0, true );
EXPECT_EQ( StyleSheetLength::Unit::Dp, pxAsDp.getUnit() );
}
UTEST( CSSFunctions, ClampHonorsMinimum ) {
auto len = StyleSheetLength::fromString( "clamp(40px, 30px, 100px)" );
Float resolved = len.asPixels( 0, Sizef::Zero, 96, 12, 12, nullptr );