Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion aui.audio/src/AUI/Audio/ASS/Property/Sound.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ namespace ass {
};


namespace prop {
namespace legacy {
template<>
struct Property<Sound>: IPropertyBase {
private:
Expand Down
19 changes: 18 additions & 1 deletion aui.core/benchmarks/SmallPimplBenchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ static void SmallPimpl_Copy(benchmark::State& state) {
}
}

template<typename Pimpl, typename Impl>
static void SmallPimpl_Move(benchmark::State& state) {
Pimpl pimpl1{Impl{}};
for (auto _ : state) {
state.PauseTiming();
Pimpl pimpl2{pimpl1};
state.ResumeTiming();
Pimpl pimpl3{std::move(pimpl2)};
benchmark::DoNotOptimize(pimpl3);
}
}

void invoke(std::function<void()>& t) {
std::invoke(t);
}
Expand All @@ -111,7 +123,6 @@ static void SmallPimpl_Invoke(benchmark::State& state) {
}



// std::function<void()> was added for comparison
BENCHMARK(SmallPimpl_Allocation<aui::small_pimpl<ITest, 128>, SmallImpl>);
BENCHMARK(SmallPimpl_Allocation<std::function<void()>, SmallImpl>);
Expand All @@ -125,6 +136,12 @@ BENCHMARK(SmallPimpl_Copy<aui::small_pimpl<ITest, 128>, MediumImpl>);
BENCHMARK(SmallPimpl_Copy<std::function<void()>, MediumImpl>);
BENCHMARK(SmallPimpl_Copy<aui::small_pimpl<ITest, 128>, BigImpl>);
BENCHMARK(SmallPimpl_Copy<std::function<void()>, BigImpl>);
BENCHMARK(SmallPimpl_Move<aui::small_pimpl<ITest, 128>, SmallImpl>);
BENCHMARK(SmallPimpl_Move<std::function<void()>, SmallImpl>);
BENCHMARK(SmallPimpl_Move<aui::small_pimpl<ITest, 128>, MediumImpl>);
BENCHMARK(SmallPimpl_Move<std::function<void()>, MediumImpl>);
BENCHMARK(SmallPimpl_Move<aui::small_pimpl<ITest, 128>, BigImpl>);
BENCHMARK(SmallPimpl_Move<std::function<void()>, BigImpl>);
BENCHMARK(SmallPimpl_Invoke<aui::small_pimpl<ITest, 128>, SmallImpl>);
BENCHMARK(SmallPimpl_Invoke<std::function<void()>, SmallImpl>);
BENCHMARK(SmallPimpl_Invoke<aui::small_pimpl<ITest, 128>, MediumImpl>);
Expand Down
50 changes: 46 additions & 4 deletions aui.core/src/AUI/Common/ASmallVector.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#pragma once

#include <cstddef>
#include <functional>

#include "ADynamicVector.h"
#include "AStaticVector.h"
Expand Down Expand Up @@ -50,19 +49,53 @@ class ASmallVector {
}

ASmallVector(ASmallVector&& rhs) noexcept {
if (rhs.isInplaceAllocated()) {
// move the static vector (copy is fine for small objects)
new (&mBase.inplace) StaticVector(std::move(*rhs.inplace()));
} else {
// move the dynamic vector, transferring ownership of the buffer
new (&mBase.dynamic) DynamicVector(std::move(*rhs.dynamic()));
}
// reset rhs to an empty inplace state
new (&rhs.mBase.inplace) StaticVector();
}

ASmallVector(const ASmallVector& rhs) noexcept {
if (rhs.isInplaceAllocated()) {
new (&mBase.inplace) StaticVector(*rhs.inplace());
} else {
new (&mBase.dynamic) DynamicVector(*rhs.dynamic());
}

new (&rhs.mBase.inplace) StaticVector();
}

~ASmallVector() noexcept {
deallocate();
}

ASmallVector& operator=(const ASmallVector& rhs) noexcept {
if (this == &rhs) return *this;
deallocate();
if (rhs.isInplaceAllocated()) {
new (&mBase.inplace) StaticVector(*rhs.inplace());
} else {
new (&mBase.dynamic) DynamicVector(*rhs.dynamic());
}
return *this;
}

ASmallVector& operator=(ASmallVector&& rhs) noexcept {
if (this == &rhs) return *this;
deallocate();
if (rhs.isInplaceAllocated()) {
new (&mBase.inplace) StaticVector(std::move(*rhs.inplace()));
} else {
new (&mBase.dynamic) DynamicVector(std::move(*rhs.dynamic()));
}
// reset rhs to empty inplace state
new (&rhs.mBase.inplace) StaticVector();
return *this;
}

[[nodiscard]]
constexpr StoredType* data() noexcept {
return reinterpret_cast<StoredType*>(mBase.common.begin);
Expand Down Expand Up @@ -587,13 +620,22 @@ class ASmallVector {
std::aligned_storage_t<sizeof(DynamicVector), alignof(DynamicVector)> dynamic;
} mBase;

const StaticVector* inplace() const {
AUI_ASSERT(isInplaceAllocated());
return reinterpret_cast<const StaticVector*>(&mBase.inplace);
}

const DynamicVector* dynamic() const {
AUI_ASSERT(!isInplaceAllocated());
return reinterpret_cast<const DynamicVector*>(&mBase.dynamic);
}

StaticVector* inplace() {
AUI_ASSERT(isInplaceAllocated());
return reinterpret_cast<StaticVector*>(&mBase.inplace);
}

DynamicVector * dynamic() {
DynamicVector* dynamic() {
AUI_ASSERT(!isInplaceAllocated());
return reinterpret_cast<DynamicVector*>(&mBase.dynamic);
}
Expand Down
10 changes: 8 additions & 2 deletions aui.core/src/AUI/Traits/values.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,17 @@ struct no_escape {
T& operator*() const noexcept { return *value; }
};


template<typename T>
concept LazyRequirements =
aui::same_as<T, void> ||
requires (T& dst, T t) { dst = std::move(t); };

/**
* @brief A value that initializes when accessed for the first time.
* @tparam T
*/
template <typename T = void>
template <LazyRequirements T = void>
struct lazy {
private:
struct EvaluationLoopTrap {};
Expand Down Expand Up @@ -337,7 +343,7 @@ struct lazy<void> {
* Unlike <code>aui::lazy</code>, internal logic of <code>aui::atomic_lazy</code> is threadsafe.
* @tparam T
*/
template <typename T>
template <LazyRequirements T>
struct atomic_lazy {
private:
mutable AMutex sync;
Expand Down
5 changes: 4 additions & 1 deletion aui.core/src/AUI/Util/APimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,7 @@ namespace aui {
private:
std::aligned_storage_t<storageSize, storageAlignment> mStorage;
};
}
}

template<typename T, std::size_t storageSize, std::size_t storageAlignment = 8>
using APimpl = aui::fast_pimpl<T, storageSize, storageAlignment>;
Comment on lines +114 to +115
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The APimpl alias is defined in the global namespace. To avoid polluting the global namespace and for better organization, it should be defined within the aui namespace, consistent with where fast_pimpl is defined.

5 changes: 5 additions & 0 deletions aui.core/src/AUI/Util/kAUI.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ using decode_type_t = typename decode_type<T>::type;
* Label { "Red text!" } AUI_OVERRIDE_STYLE { TextColor { AColor::RED } },
* });
* ```
*
* Former names:
*
* - `AUI_WITH_STYLE`
* - `with_style`
*/
#define AUI_OVERRIDE_STYLE &ass::PropertyListRecursive

Expand Down
42 changes: 42 additions & 0 deletions aui.core/tests/SmallVectorTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,45 @@ TEST_F(SmallVector, EraseFromMiddle) {
EXPECT_EQ(*vector[1], 2);
EXPECT_EQ(*vector[2], 3);
}


// Test copy constructor when vector is still in-place (size <= StaticVectorSize)
TEST_F(SmallVector, CopyConstructorInPlace) {
ASmallVector<std::shared_ptr<int>, 2> original;
original.push_back(std::make_shared<int>(10));
original.push_back(std::make_shared<int>(20));

ASmallVector<std::shared_ptr<int>, 2> copy(original);

EXPECT_EQ(copy.size(), original.size());
EXPECT_EQ(*copy[0], 10);
EXPECT_EQ(*copy[1], 20);

// Modify original and ensure copy remains unchanged
original.pop_back();
EXPECT_EQ(original.size(), 1u);
EXPECT_EQ(copy.size(), 2u);
}

// Test copy constructor when vector has switched to dynamic allocation
TEST_F(SmallVector, CopyConstructorDynamic) {
ASmallVector<std::shared_ptr<int>, 2> original;
original.push_back(std::make_shared<int>(1));
original.push_back(std::make_shared<int>(2));
original.push_back(std::make_shared<int>(3)); // triggers dynamic
original.push_back(std::make_shared<int>(4));

ASmallVector<std::shared_ptr<int>, 2> copy(original);

EXPECT_EQ(copy.size(), original.size());
EXPECT_EQ(*copy[0], 1);
EXPECT_EQ(*copy[1], 2);
EXPECT_EQ(*copy[2], 3);
EXPECT_EQ(*copy[3], 4);

// Modify original and ensure copy remains unchanged
original.erase(original.begin() + 1); // remove element 2
EXPECT_EQ(original.size(), 3u);
EXPECT_EQ(copy.size(), 4u);
EXPECT_EQ(*copy[1], 2); // copy still has 2
}
47 changes: 43 additions & 4 deletions aui.uitests/tests/UIReactiveTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "AUI/Test/UI/UITestCase.h"
#include "AUI/Test/UI/Assertion/Color.h"
#include "AUI/Util/Declarative/Containers.h"
#include "AUI/View/ACheckBox.h"

namespace {

Expand Down Expand Up @@ -40,26 +41,64 @@ TEST_F(UIReactiveTest, Label) {
// Test that a reactive label updates its text when the bound property changes.
// The test creates a simple state object with a reactive string property
// and binds it to a label using the AUI_REACT macro. It then verifies
// the initial rendered text and the updated text after changing the
// the initially rendered text and the updated text after changing the
// property.
using namespace ass;

struct State {
AProperty<AString> name;
AProperty<AString> name = "Test";
};
auto state = _new<State>();

mWindow->setContents(Vertical {
Label { AUI_REACT("{}!"_format(state->name)) }
});

// Initially, the property is empty, so the label should display "!".
EXPECT_EQ(*_cast<ALabel>(By::type<ALabel>().one())->text(), "!");
// Initially, the property equals to "Test", so the label should display "Test!".
EXPECT_EQ(*_cast<ALabel>(By::type<ALabel>().one())->text(), "Test!");
// Update the property; the label should automatically reflect the new value.
state->name = "Hello";
EXPECT_EQ(*_cast<ALabel>(By::type<ALabel>().one())->text(), "Hello!");
}

struct TextColor {
AColor color;

void operator()(AView& view) const {

}
};

TEST_F(UIReactiveTest, Test2) {
// Test that a reactive label updates its text when the bound property changes.
// The test creates a simple state object with a reactive bool property
// and binds it to a label using the AUI_REACT macro. It then verifies
// the initial rendered text and the updated text after changing the
// property.
using namespace ass;

struct State {
AProperty<bool> option = false;
};
auto state = _new<State>();

mWindow->setContents(Vertical {
Label {
.text = "Test",
.modifier = AUI_REACT(Modifier {} | ::TextColor { state->option ? AColor::GREEN : AColor::RED }),
},
});

// Initially, the property equals to `false`, so the label should be red.
uitest::frame();
EXPECT_EQ(_cast<ALabel>(By::type<ALabel>().one())->textColor(), AColor::RED);

// Update the property; the label should automatically reflect the new value.
state->option = true;
uitest::frame();
EXPECT_EQ(_cast<ALabel>(By::type<ALabel>().one())->textColor(), AColor::GREEN);
}
Comment on lines +72 to +100
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This test case has several issues and appears to be incomplete:

  1. Non-descriptive name (L72): The test name Test2 should be renamed to describe what's being tested (e.g., ReactiveStyleModifier).
  2. Incomplete modifier (L67-69): The TextColor::operator() is empty, so the modifier has no effect.
  3. Empty block (L97-99): The AUI_LET block is empty.
  4. Incorrect assertions (L103-105): The assertions and their comments seem to be copy-pasted from another test. The expected values ("!", "Hello!") do not match what the code produces, and the state is not updated between assertions.

The test should be completed to properly verify the reactive modifier functionality. For example, it could check the textColor property of the ALabel after changing state->option.


/*
TEST_F(UIReactiveTest, MultipleWithStyle) {
// Test that multiple containers with overridden styles render correctly.
Expand Down
2 changes: 1 addition & 1 deletion aui.views/src/AUI/ASS/Property/ATextOverflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
#include "ATextOverflow.h"


void ass::prop::Property<ATextOverflow>::applyFor(AView* view) {
void ass::legacy::Property<ATextOverflow>::applyFor(AView* view) {
AUI_NULLSAFE(dynamic_cast<AAbstractLabel*>(view))->setTextOverflow(mInfo);
}
2 changes: 1 addition & 1 deletion aui.views/src/AUI/ASS/Property/ATextOverflow.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#include "IProperty.h"

namespace ass {
namespace prop {
namespace legacy {
template<>
struct API_AUI_VIEWS Property<ATextOverflow>: IPropertyBase {
private:
Expand Down
8 changes: 4 additions & 4 deletions aui.views/src/AUI/ASS/Property/Backdrop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
#include "AUI/Render/IRenderer.h"
#include "IProperty.h"

void ass::prop::Property<ass::Backdrop>::renderFor(AView* view, const ARenderContext& ctx) {
void ass::legacy::Property<ass::Backdrop>::renderFor(AView* view, const ARenderContext& ctx) {
ctx.render.backdrops({}, view->getSize(), std::span<ass::Backdrop::Any>(mInfo.effects.data(), mInfo.effects.size()));
}

bool ass::prop::Property<ass::Backdrop>::isNone() { return mInfo.effects.empty(); }
bool ass::legacy::Property<ass::Backdrop>::isNone() { return mInfo.effects.empty(); }

ass::prop::PropertySlot ass::prop::Property<ass::Backdrop>::getPropertySlot() const {
return ass::prop::PropertySlot::BACKDROP;
ass::legacy::PropertySlot ass::legacy::Property<ass::Backdrop>::getPropertySlot() const {
return ass::legacy::PropertySlot::BACKDROP;
}
ass::Backdrop::GaussianBlurCustom ass::Backdrop::GaussianBlur::findOptimalParams() const {
static constexpr auto MIN_OPTIMAL_DOWNSCALE = 1;
Expand Down
2 changes: 1 addition & 1 deletion aui.views/src/AUI/ASS/Property/Backdrop.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ namespace ass {
Backdrop(std::initializer_list<Any> effects): effects(std::move(effects)) {}
};

namespace prop {
namespace legacy {
template<>
struct API_AUI_VIEWS Property<Backdrop>: IPropertyBase {
private:
Expand Down
2 changes: 1 addition & 1 deletion aui.views/src/AUI/ASS/Property/BackgroundCropping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@



void ass::prop::Property<ass::BackgroundCropping>::applyFor(AView* view) {
void ass::legacy::Property<ass::BackgroundCropping>::applyFor(AView* view) {
view->getAssHelper()->state.backgroundCropping = mInfo;
}

2 changes: 1 addition & 1 deletion aui.views/src/AUI/ASS/Property/BackgroundCropping.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace ass {
static const BackgroundCropping H4_4;
};

namespace prop {
namespace legacy {
template<>
struct API_AUI_VIEWS Property<BackgroundCropping>: IPropertyBase {
private:
Expand Down
8 changes: 4 additions & 4 deletions aui.views/src/AUI/ASS/Property/BackgroundEffect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#include "IProperty.h"


void ass::prop::Property<ass::BackgroundEffect>::renderFor(AView* view, const ARenderContext& ctx) {
void ass::legacy::Property<ass::BackgroundEffect>::renderFor(AView* view, const ARenderContext& ctx) {
for (auto& e : mInfo.mEffects)
{
e->draw(view);
Expand All @@ -27,11 +27,11 @@ void ass::prop::Property<ass::BackgroundEffect>::renderFor(AView* view, const AR
IPropertyBase::renderFor(view, ctx);
}

bool ass::prop::Property<ass::BackgroundEffect>::isNone() {
bool ass::legacy::Property<ass::BackgroundEffect>::isNone() {
return mInfo.mEffects.empty();
}
ass::prop::PropertySlot ass::prop::Property<ass::BackgroundEffect>::getPropertySlot() const {
return ass::prop::PropertySlot::BACKGROUND_EFFECT;
ass::legacy::PropertySlot ass::legacy::Property<ass::BackgroundEffect>::getPropertySlot() const {
return ass::legacy::PropertySlot::BACKGROUND_EFFECT;
}


Loading
Loading