فهرست منبع

Data expressions 'null' type and pointers

Co-authored-by: Omegapl <[email protected]>
Michael Ragazzon 3 سال پیش
والد
کامیت
3fd100e479

+ 24 - 0
Include/RmlUi/Core/TypeConverter.inl

@@ -391,6 +391,30 @@ public:
 	}
 };
 
+template <>
+class TypeConverter<void*, String> {
+public:
+	static bool Convert(void*& src, String& dest) { return FormatString(dest, 32, "0x%p", src) > 0; }
+};
+template <>
+class TypeConverter<void*, bool> {
+public:
+	static bool Convert(void*& src, bool& dest)
+	{
+		dest = (src != nullptr);
+		return true;
+	}
+};
+template <>
+class TypeConverter<void*, double> {
+public:
+	static bool Convert(void*& src, double& dest)
+	{
+		dest = (src == nullptr ? 0.0 : 1.0);
+		return true;
+	}
+};
+
 template< typename SourceType, typename InternalType, int count >
 class TypeConverterVectorString
 {

+ 32 - 0
Samples/basic/databinding/data/databinding.rml

@@ -298,6 +298,38 @@ select selectbox option.disabled {
 	<p data-if="show_text">The quick brown fox jumps over the lazy {{animal}}.</p>
 	<input type="text" data-value="animal"/>
 </panel>
+<tab>Pointers</tab>
+<panel data-model="basicptrs">
+	<h1>{{title}}</h1>
+	<p>This is null struct pointer: {{ptr_null}}</p>
+	<p>This is non-null struct pointer: {{ptr}}</p>
+	<p>This is null scalar pointer: {{i_null}}</p>
+	<p>This is non-null scalar pointer: {{i}}</p>
+	
+	<h1>+1</h1>
+	<p>This is null struct pointer: {{ptr_null + 1}}</p>
+	<p>This is non-null struct pointer: {{ptr + 1}}</p>
+	<p>This is null scalar pointer: {{i_null + 1}}</p>
+	<p>This is non-null scalar pointer: {{i + 1}}</p>
+	
+	<h1>+'a'</h1>
+	<p>This is null struct pointer: {{ptr_null + 'a'}}</p>
+	<p>This is non-null struct pointer: {{ptr + 'a'}}</p>
+	<p>This is null scalar pointer: {{i_null + 'a'}}</p>
+	<p>This is non-null scalar pointer: {{i + 'a'}}</p>
+	
+	<h1>| default('TBA')</h1>
+	<p>This is null struct pointer: {{ptr_null | default('TBA')}}</p>
+	<p>This is non-null struct pointer: {{ptr | default('TBA')}}</p>
+	<p>This is null scalar pointer: {{i_null | default('TBA')}}</p>
+	<p>This is non-null scalar pointer: {{i | default('TBA')}}</p>
+	
+	<h1>data-if</h1>
+	<p data-if="ptr_null">This is null struct pointer</p>
+	<p data-if="ptr">This is non-null struct pointer</p>
+	<p data-if="i_null">This is null scalar pointer</p>
+	<p data-if="i">This is non-null scalar pointer</p>
+</panel>
 <tab>Events</tab>
 <panel id="welcome" data-model="events">
 	<p class="title" style="margin-top: 1.8em;">{{hello_world}}<span style="color: blue;"> Rated: {{rating}}</span></p>

+ 38 - 0
Samples/basic/databinding/src/main.cpp

@@ -58,6 +58,43 @@ namespace BasicExample {
 	}
 }
 
+namespace BasicPointersExample {
+
+Rml::DataModelHandle model_handle;
+
+struct Foo {
+	unsigned int i = 11;
+};
+
+struct MyPtrData {
+	Rml::String title = "Simple pointer data binding example";
+	Foo* ptr_null = nullptr;
+	Foo* ptr = new Foo();
+	unsigned int* i_null = nullptr;
+	unsigned int* i = new unsigned int(15);
+} my_data;
+
+bool Initialize(Rml::Context* context)
+{
+	Rml::DataModelConstructor constructor = context->CreateDataModel("basicptrs");
+	if (!constructor)
+		return false;
+
+	auto foostruct = constructor.RegisterStruct<Foo>();
+	foostruct.RegisterMember("i", &Foo::i);
+
+	constructor.Bind("title", &my_data.title);
+	constructor.Bind("ptr_null", &my_data.ptr_null);
+	constructor.Bind("ptr", &my_data.ptr);
+	constructor.Bind("i_null", &my_data.i_null);
+	constructor.Bind("i", &my_data.i);
+
+	model_handle = constructor.GetModelHandle();
+
+	return true;
+}
+} // namespace BasicPointersExample
+
 
 namespace EventsExample {
 
@@ -490,6 +527,7 @@ int main(int /*argc*/, char** /*argv*/)
 
 	if (!context
 		|| !BasicExample::Initialize(context)
+		|| !BasicPointersExample::Initialize(context)
 		|| !EventsExample::Initialize(context)
 		|| !InvadersExample::Initialize(context)
 		|| !FormsExample::Initialize(context)

+ 8 - 6
Source/Core/DataExpression.cpp

@@ -591,6 +591,8 @@ namespace Parse {
 			parser.Emit(Instruction::Literal, Variant(true));
 		else if (name == "false")
 			parser.Emit(Instruction::Literal, Variant(false));
+		else if (name == "null")
+			parser.Emit(Instruction::Literal, Variant());
 		else if (parser.Look() == '(')
 		{
 			if (!valid_function_name)
@@ -769,8 +771,8 @@ static String DumpProgram(const Program& program)
 	String str;
 	for (size_t i = 0; i < program.size(); i++)
 	{
-		String instruction_str = program[i].data.Get<String>();
-		str += CreateString(50 + instruction_str.size(), "  %4zu  '%c'  %s\n", i, char(program[i].instruction), instruction_str.c_str());
+		String data_str = program[i].data.Get<String>();
+		str += CreateString(50 + data_str.size(), "  %4zu  '%c'  %s\n", i, char(program[i].instruction), data_str.c_str());
 	}
 	return str;
 }
@@ -872,7 +874,7 @@ private:
 		case Instruction::Add:
 		{
 			if (AnyString(L, R))
-				R = Variant(L.Get<String>() + R.Get<String>());
+				R = Variant(L.Get<String>("null") + R.Get<String>("null"));
 			else
 				R = Variant(L.Get<double>() + R.Get<double>());
 		}
@@ -890,7 +892,7 @@ private:
 		case Instruction::Equal:
 		{
 			if (AnyString(L, R))
-				R = Variant(L.Get<String>() == R.Get<String>());
+				R = Variant(L.Get<String>("null") == R.Get<String>("null"));
 			else
 				R = Variant(L.Get<double>() == R.Get<double>());
 		}
@@ -898,7 +900,7 @@ private:
 		case Instruction::NotEqual:
 		{
 			if (AnyString(L, R))
-				R = Variant(L.Get<String>() != R.Get<String>());
+				R = Variant(L.Get<String>("null") != R.Get<String>("null"));
 			else
 				R = Variant(L.Get<double>() != R.Get<double>());
 		}
@@ -930,7 +932,7 @@ private:
 				String arguments_str;
 				for (size_t i = 0; i < arguments.size(); i++)
 				{
-					arguments_str += arguments[i].Get<String>();
+					arguments_str += arguments[i].Get<String>("null");
 					if (i < arguments.size() - 1)
 						arguments_str += ", ";
 				}

+ 4 - 3
Source/Core/DataModel.cpp

@@ -78,7 +78,7 @@ static DataAddress ParseAddress(const String& address_str)
 // Returns an error string on error, or nullptr on success.
 static const char* LegalVariableName(const String& name)
 {
-	static SmallUnorderedSet<String> reserved_names{ "it", "ev", "true", "false", "size", "literal" };
+	static SmallUnorderedSet<String> reserved_names{ "it", "ev", "true", "false", "null", "size", "literal" };
 	
 	if (name.empty())
 		return "Name cannot be empty.";
@@ -323,8 +323,9 @@ const DataEventFunc* DataModel::GetEventCallback(const String& name)
 bool DataModel::GetVariableInto(const DataAddress& address, Variant& out_value) const {
 	DataVariable variable = GetVariable(address);
 	bool result = (variable && variable.Get(out_value));
-	if (!result)
-		Log::Message(Log::LT_WARNING, "Could not get value from data variable '%s'.", DataAddressToString(address).c_str());
+	// TODO: Print warning in some situations such as with invalid struct child name, but not on nullptrs.
+	//if (!result)
+	//	Log::Message(Log::LT_WARNING, "Could not get value from data variable '%s'.", DataAddressToString(address).c_str());
 	return result;
 }
 

+ 6 - 0
Source/Core/DataTypeRegister.cpp

@@ -100,6 +100,12 @@ DataTypeRegister::DataTypeRegister()
 			return {};
 		return Variant(Math::RoundFloat(value));
 	});
+
+	transform_register.Register("default", [](const VariantList& arguments) -> Variant {
+		if (arguments.size() != 2)
+			return {};
+		return arguments[0].GetType() == Variant::NONE ? arguments[1] : arguments[0];
+	});
 }
 
 DataTypeRegister::~DataTypeRegister() {}

+ 10 - 5
Source/Core/DataVariable.cpp

@@ -51,9 +51,10 @@ DataVariableType DataVariable::Type() {
 }
 
 
-bool VariableDefinition::Get(void* /*ptr*/, Variant& /*variant*/) {
-    Log::Message(Log::LT_WARNING, "Values can only be retrieved from scalar data types.");
-    return false;
+bool VariableDefinition::Get(void* ptr, Variant& variant) {
+	
+		variant = ptr;
+    return true;
 }
 bool VariableDefinition::Set(void* /*ptr*/, const Variant& /*variant*/) {
     Log::Message(Log::LT_WARNING, "Values can only be assigned to scalar data types.");
@@ -141,7 +142,9 @@ BasePointerDefinition::BasePointerDefinition(VariableDefinition* underlying_defi
 
 bool BasePointerDefinition::Get(void* ptr, Variant& variant)
 {
-    return underlying_definition->Get(DereferencePointer(ptr), variant);
+	if (void* next_ptr = DereferencePointer(ptr))
+		return underlying_definition->Get(next_ptr, variant);
+	return false;
 }
 
 bool BasePointerDefinition::Set(void* ptr, const Variant& variant)
@@ -156,7 +159,9 @@ int BasePointerDefinition::Size(void* ptr)
 
 DataVariable BasePointerDefinition::Child(void* ptr, const DataAddressEntry& address)
 {
-    return underlying_definition->Child(DereferencePointer(ptr), address);
+	if (void* next_ptr = DereferencePointer(ptr))
+		return underlying_definition->Child(next_ptr, address);
+	return DataVariable();
 }
 
 } // namespace Rml

+ 4 - 4
Source/Core/DataViewDefault.cpp

@@ -99,7 +99,7 @@ bool DataViewAttribute::Update(DataModel& model)
 
 	if (element && GetExpression().Run(expr_interface, variant))
 	{
-		const String value = variant.Get<String>();
+		const String value = variant.Get<String>("null");
 		const Variant* attribute = element->GetAttribute(attribute_name);
 		
 		if (!attribute || (attribute && attribute->Get<String>() != value))
@@ -196,7 +196,7 @@ bool DataViewStyle::Update(DataModel& model)
 	
 	if (element && GetExpression().Run(expr_interface, variant))
 	{
-		const String value = variant.Get<String>();
+		const String value = variant.Get<String>("null");
 		const Property* p = element->GetLocalProperty(property_name);
 		if (!p || p->Get<String>() != value)
 		{
@@ -245,7 +245,7 @@ bool DataViewRml::Update(DataModel & model)
 
 	if (element && GetExpression().Run(expr_interface, variant))
 	{
-		String new_rml = variant.Get<String>();
+		String new_rml = variant.Get<String>("null");
 		if (new_rml != previous_rml)
 		{
 			element->SetInnerRML(new_rml);
@@ -395,7 +395,7 @@ bool DataViewText::Update(DataModel& model)
 			RMLUI_ASSERT(entry.data_expression);
 			Variant variant;
 			bool result = entry.data_expression->Run(expression_interface, variant);
-			const String value = variant.Get<String>();
+			const String value = variant.Get<String>("null");
 			if (result && entry.value != value)
 			{
 				entry.value = value;

+ 229 - 1
Tests/Source/UnitTests/DataModel.cpp

@@ -27,8 +27,10 @@
  */
 
 #include "../../../Source/Core/DataModel.cpp"
-#include <RmlUi/Core/Types.h>
+#include "RmlUi/Core/Core.h"
+#include "RmlUi/Core/SystemInterface.h"
 #include <RmlUi/Core/DataModelHandle.h>
+#include <RmlUi/Core/Types.h>
 #include <doctest.h>
 
 using namespace Rml;
@@ -116,3 +118,229 @@ TEST_CASE("Data variables")
 		CHECK(get_result.Get<String>() == "90");
 	}
 }
+
+struct YesStr {
+	int scalar;
+	int* scalarptr;
+	const int* scalarptr2;
+	Vector<int> yesvector;
+};
+struct FooStr {
+	String scalar_string = "yes";
+	int scalar;
+	int* scalarptr;
+	YesStr* yesptr;
+	SharedPtr<YesStr> yesshr;
+	const int* scalarptr2;
+	Vector<int> foovector;
+	Vector<int>* foovectorptr;
+};
+struct BarStr {
+	int scalar;
+	int* scalarptr;
+	const int* scalarptr2;
+	FooStr foo;
+	FooStr* fooptr;
+	SharedPtr<FooStr> fooshr;
+	Vector<int> barvector;
+	Vector<int>* barvectorptr;
+};
+
+void registerStructs(DataModelConstructor& handle)
+{
+	handle.RegisterArray<Vector<int>>();
+
+	if (auto fun_handle = handle.RegisterStruct<YesStr>())
+	{
+		fun_handle.RegisterMember("scalar", &YesStr::scalar);
+		fun_handle.RegisterMember("scalarptr", &YesStr::scalarptr);
+		// requires constness support - fun_handle.RegisterMember("scalarptr2", &YesStr::scalarptr2);
+		fun_handle.RegisterMember("yesvector", &YesStr::yesvector);
+	}
+
+	if (auto fun_handle = handle.RegisterStruct<FooStr>())
+	{
+		fun_handle.RegisterMember("scalar", &FooStr::scalar);
+		fun_handle.RegisterMember("scalarptr", &FooStr::scalarptr);
+		// requires constness support - fun_handle.RegisterMember("scalarptr2", &FooStr::scalarptr2);
+		fun_handle.RegisterMember("string", &FooStr::scalar_string);
+		fun_handle.RegisterMember("yesptr", &FooStr::yesptr);
+		fun_handle.RegisterMember("yesshr", &FooStr::yesshr);
+		fun_handle.RegisterMember("foovector", &FooStr::foovector);
+		fun_handle.RegisterMember("foovectorptr", &FooStr::foovectorptr);
+	}
+
+	if (auto smart_handle = handle.RegisterStruct<BarStr>())
+	{
+		smart_handle.RegisterMember("scalar", &BarStr::scalar);
+		smart_handle.RegisterMember("scalarptr", &BarStr::scalarptr);
+		// requires constness support - smart_handle.RegisterMember("scalarptr2", &BarStr::scalarptr2);
+		smart_handle.RegisterMember("foo", &BarStr::foo);
+		smart_handle.RegisterMember("fooptr", &BarStr::fooptr);
+		smart_handle.RegisterMember("fooshr", &BarStr::fooshr);
+		smart_handle.RegisterMember("barvector", &BarStr::barvector);
+		smart_handle.RegisterMember("barvectorptr", &BarStr::barvectorptr);
+	}
+}
+
+TEST_CASE("Data variables pointers")
+{
+	DataModel model;
+	DataTypeRegister types;
+
+	DataModelConstructor handle(&model, &types);
+
+	// Setup data type register
+	registerStructs(handle);
+
+	BarStr example;
+	handle.Bind("example", &example);
+	{
+		int for_scalars_ptrsa = 13;
+		int for_scalars_ptrsb = 69;
+		example.scalar = 666;
+		example.scalarptr = &for_scalars_ptrsa;
+		example.scalarptr2 = &for_scalars_ptrsa;
+		example.foo.scalar = 1337;
+		example.foo.scalarptr = &for_scalars_ptrsb;
+		example.foo.scalarptr2 = &for_scalars_ptrsb;
+		example.fooptr = new FooStr();
+		example.fooptr->scalar = 1337;
+		example.fooptr->scalarptr = &for_scalars_ptrsb;
+		example.fooptr->scalarptr2 = &for_scalars_ptrsb;
+		example.fooshr = std::make_shared<FooStr>();
+		*example.fooshr = example.foo;
+
+		Vector<String> test_addresses = {"example.scalar", "example.scalarptr", /*"example.scalarptr2", */
+			"example.foo.scalar", "example.foo.scalarptr", /*const "example.foo.scalarptr2", */ "example.foo.string", "example.fooptr.scalar",
+			"example.fooptr.scalarptr", /*const "example.fooptr.scalarptr2" ,*/ "example.fooptr.string", "example.fooshr.scalar",
+			"example.fooshr.scalarptr", /*const "example.fooshr.scalarptr2", */ "example.fooshr.string"};
+		Vector<String> expected_results = {ToString(example.scalar), ToString(*example.scalarptr), /*const ToString(*example.scalarptr2), */
+			ToString(example.foo.scalar), ToString(*example.foo.scalarptr),
+			/*const ToString(*example.foo.scalarptr2) ,*/ ToString(example.foo.scalar_string), ToString(example.fooptr->scalar),
+			ToString(*example.fooptr->scalarptr), /*const ToString(*example.fooptr->scalarptr2),*/ ToString(example.fooptr->scalar_string),
+			ToString(example.fooshr->scalar), ToString(*example.fooshr->scalarptr),
+			/*const ToString(*example.fooshr->scalarptr2) ,*/ ToString(example.fooshr->scalar_string)};
+
+		Vector<String> results;
+
+		for (auto& str_address : test_addresses)
+		{
+			DataAddress address = ParseAddress(str_address);
+
+			Variant result;
+			if (model.GetVariableInto(address, result))
+				results.push_back(result.Get<String>());
+		}
+		CHECK(results == expected_results);
+	}
+	{
+		delete example.fooptr;
+		int new_int_value = 88;
+		example.fooptr = new FooStr();
+		example.fooptr->scalar = 2137;
+		example.fooptr->scalarptr = &new_int_value;
+		example.fooptr->scalarptr2 = &new_int_value;
+		example.fooshr = std::make_shared<FooStr>();
+		*example.fooshr = example.foo;
+
+		Vector<String> test_addresses = {"example.fooptr.scalar", "example.fooptr.scalarptr",
+			/*const "example.fooptr.scalarptr2" , */ "example.fooptr.string", "example.fooshr.scalar", "example.fooshr.scalarptr",
+			/*const "example.fooshr.scalarptr2" , */ "example.fooshr.string"};
+		Vector<String> expected_results = {ToString(example.fooptr->scalar), ToString(*example.fooptr->scalarptr),
+			/*const ToString(*example.fooptr->scalarptr2),*/ ToString(example.fooptr->scalar_string), ToString(example.fooshr->scalar),
+			ToString(*example.fooshr->scalarptr), /*const ToString(*example.fooshr->scalarptr2),*/ ToString(example.fooshr->scalar_string)};
+
+		Vector<String> results;
+
+		for (auto& str_address : test_addresses)
+		{
+			DataAddress address = ParseAddress(str_address);
+
+			Variant result;
+			if (model.GetVariableInto(address, result))
+				results.push_back(result.Get<String>());
+		}
+		CHECK(results == expected_results);
+	}
+}
+
+TEST_CASE("Data variables pointers - nulls")
+{
+	class SystemInterfaceImpl : public SystemInterface {
+	public:
+		double GetElapsedTime() override { return 0; }
+	};
+	SystemInterface* obj = new SystemInterfaceImpl();
+	SetSystemInterface(obj);
+	// Setup data type register
+
+	DataModel model;
+	DataTypeRegister types;
+
+	DataModelConstructor handle(&model, &types);
+
+	// Setup data type register
+	registerStructs(handle);
+
+	BarStr example;
+	BarStr* exampleptr;
+	UniquePtr<BarStr> exampleunq;
+	UniquePtr<BarStr> exampleunq2;
+	handle.Bind("example", &example);
+	handle.Bind("exampleptr", &exampleptr);
+	handle.Bind("exampleunq", &exampleunq);
+	handle.Bind("exampleunq2", &exampleunq2);
+	{
+		example.fooptr = nullptr;
+		example.fooshr = std::shared_ptr<FooStr>();
+		example.barvectorptr = nullptr;
+		exampleptr = nullptr;
+		exampleunq = MakeUnique<BarStr>();
+		exampleunq->fooptr = nullptr;
+		exampleunq2 = MakeUnique<BarStr>();
+		exampleunq2->fooptr = new FooStr();
+		exampleunq2->fooptr->scalarptr = nullptr;
+		exampleunq2->fooptr->yesptr = nullptr;
+
+		Vector<String> test_addresses = {
+			"example.fooptr.scalarptr",
+			"example.fooptr",
+			"example.fooshr",
+			"example.fooshr.scalarptr",
+			"example.fooptr.yesptr",
+			"example.fooptr.yesptr.scalar",
+			"example.fooptr.yesshr",
+			"example.fooptr.yesshr.scalar",
+			"example.barvectorptr",
+			"example.barvectorptr[0]",
+			"example.fooptr.foovectorptr",
+			"example.fooptr.foovectorptr[0]",
+			"exampleptr",
+			"exampleptr.barvectorptr",
+			"exampleptr.barvectorptr[0]",
+			"exampleptr.fooptr",
+			"exampleptr.fooptr.foovectorptr",
+			"exampleptr.fooptr.foovectorptr[0]",
+			/*"exampleunq", - this is not null*/ "exampleunq.fooptr",
+			"exampleunq.fooptr.yesptr",
+			/*"exampleunq2", - this is not null*/ /*"exampleunq2.fooptr", - this is not null*/ "exampleunq2.fooptr.scalarptr",
+			"exampleunq2.fooptr.yesptr",
+
+		};
+		Vector<void*> expected_results = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
+			nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
+
+		Vector<void*> results;
+
+		for (auto& str_address : test_addresses)
+		{
+			DataAddress address = ParseAddress(str_address);
+
+			Variant result;
+			model.GetVariableInto(address, result);
+			results.push_back(result.Get<void*>());
+		}
+		CHECK(results == expected_results);
+	}
+}