"Every value can tell you what it means."
PowerProps is a lightweight C++ runtime property system for making application state discoverable.
Instead of teaching every editor, serializer, plugin, configuration system, and tool how to understand every piece of data in your application, describe that data once.
Everything else can discover it at runtime.
Applications are full of values that mean something.
A filename isn't just a string.
A position isn't just three floats.
An output format isn't just an integer.
A checkbox isn't just a boolean.
The code that owns those values knows what they mean, but the rest of the application usually doesn't. Editors need custom code to expose them. Serializers need custom code to preserve them. Plugins need custom interfaces to find them.
PowerProps gives that information a common language.
A property has a name, a type, a value, and an identity. It can also carry additional information describing what that value means - an aspect.
Systems that understand properties can work with those values without having been written specifically for the code that provides them.
Describe the state once. Let everything else discover it.
Most mature applications eventually build variations of the same machinery:
- runtime inspection
- serialization
- configuration
- property editing
- scripting interfaces
- plugin interfaces
- debugging and diagnostic tools
Usually each system gets its own way of finding and manipulating application state.
PowerProps approaches the problem from the opposite direction.
Instead of teaching every consumer how to understand every provider, give application state a common vocabulary.
The provider describes what is available.
The consumer decides what to do with it.
Properties live inside an IPropertySet.
Create one like this:
props::IPropertySet* properties =
props::IPropertySet::CreatePropertySet();A property set is simply a discoverable collection of related properties.
For example, a packaging configuration might expose:
Package
├── OutputPath
├── CompressionLevel
├── GenerateManifest
└── PackageName
The code consuming that set does not need to know what subsystem created it.
It can enumerate everything that is available:
for (size_t i = 0;
i < properties->GetPropertyCount();
i++)
{
props::IProperty* property =
properties->GetProperty(i);
// Inspect its name, ID, type,
// aspect, flags, value...
}Or retrieve an individual property by name:
props::IProperty* compression =
properties->GetPropertyByName(
_T("CompressionLevel"));by ID:
props::IProperty* compression =
properties->GetPropertyById('COMP');or through the indexing interface:
props::IProperty* compression =
(*properties)['COMP'];The property set provides the common point of discovery.
Create a property through its property set:
props::IProperty* compression =
properties->CreateProperty(
_T("CompressionLevel"),
'COMP');Then give it a value:
compression->SetInt(5);The property now knows that it contains an integer.
Its value can be read later with:
int64_t level =
compression->AsInt();A string property works the same way:
props::IProperty* output_path =
properties->CreateProperty(
_T("OutputPath"),
'PATH');
output_path->SetString(
_T("C:\\Build\\Output"));And a boolean:
props::IProperty* manifest =
properties->CreateProperty(
_T("GenerateManifest"),
'MNFT');
manifest->SetBool(true);The values themselves aren't unusual.
What PowerProps adds is a common way to discover what they are, identify them, inspect them, and work with them generically.
Sometimes knowing the data type isn't enough.
Consider this:
props::IProperty* output_file =
properties->CreateProperty(
_T("OutputFile"),
'OUTF');
output_file->SetString(
_T("installer.exe"));PowerProps knows that OutputFile is a string.
But a generic editor still doesn't know what kind of string it represents.
Add an aspect:
output_file->SetAspect(
props::IProperty::PA_FILENAME);Now a consumer can know that this isn't merely arbitrary text.
An editor could present a file browser.
A directory works the same way:
output_path->SetAspect(
props::IProperty::PA_DIRECTORY);A float can describe a percentage:
props::IProperty* progress =
properties->CreateProperty(
_T("Progress"),
'PRGS');
progress->SetFloat(75.0f);
progress->SetAspect(
props::IProperty::PA_PERCENTAGE);A vector can represent a color:
props::IProperty* tint =
properties->CreateProperty(
_T("Tint"),
'TINT');
tint->SetVec3F(
props::TVec3F(
1.0f,
0.5f,
0.25f));
tint->SetAspect(
props::IProperty::PA_COLOR_RGB);Or that same underlying vector type might represent latitude and longitude, a rotation, eye position, sun direction, dimensions, or something entirely different.
The property type says:
What is this data?
The aspect says:
What does this data mean?
That distinction allows generic tools to make much better decisions about values they have never seen before.
PowerProps does not require application state to live inside the property system.
A reference property can expose data that already exists somewhere else.
For example:
props::TVec3F model_position;
props::TVec3F model_orientation;
props::TVec3F model_scale(
1.0f,
1.0f,
1.0f);Expose those values directly:
properties->CreateReferenceProperty(
_T("ModelPosition"),
'POSN',
&model_position,
props::IProperty::PT_FLOAT_V3);
properties->CreateReferenceProperty(
_T("ModelOrientation"),
'ORNT',
&model_orientation,
props::IProperty::PT_FLOAT_V3);
properties->CreateReferenceProperty(
_T("ModelScale"),
'SCAL',
&model_scale,
props::IProperty::PT_FLOAT_V3);Those properties do not contain duplicate copies of the values.
They refer directly to the existing application state.
If an editor changes "ModelPosition", it changes model_position.
If application code changes model_position, the property immediately reflects the new value.
There is nothing to synchronize.
This makes reference properties particularly useful when adding discoverability to an existing system without reorganizing where its state lives.
Properties don't have to describe an "object."
Consider an application with packaging settings:
OutputPath
CompressionLevel
GenerateManifest
PackageName
A property set can describe them:
props::IPropertySet* package =
props::IPropertySet::CreatePropertySet();
props::IProperty* name =
package->CreateProperty(
_T("PackageName"),
'NAME');
name->SetString(
_T("MyApplication"));
props::IProperty* output =
package->CreateProperty(
_T("OutputPath"),
'PATH');
output->SetString(
_T("C:\\Build\\Output"));
output->SetAspect(
props::IProperty::PA_DIRECTORY);
props::IProperty* compression =
package->CreateProperty(
_T("CompressionLevel"),
'COMP');
compression->SetInt(5);
props::IProperty* manifest =
package->CreateProperty(
_T("GenerateManifest"),
'MNFT');
manifest->SetBool(true);A configuration editor doesn't need to know anything about packaging.
It asks what properties are available.
PowerProps tells it.
The editor handles the types and aspects it understands and presents them appropriately.
The same property set might later be consumed by a serializer, scripting system, plugin, command-line tool, or something that did not exist when the properties were originally defined.
Imagine an application exposes:
OutputPath string / directory
CompressionLevel integer
GenerateManifest boolean
PackageName string
An editor might turn those properties into:
Output Path: [ C:\Build\Output ] [...]
Compression Level: [ 5 ]
Generate Manifest: [✓]
Package Name: [ MyApplication ]
A serializer could preserve the same information.
A plugin could inspect it programmatically.
A debugging tool could display it.
A scripting layer could expose it.
None of those systems need a special interface for packaging.
They only need to understand properties.
A consumer can inspect a property set without knowing ahead of time what it contains:
for (size_t i = 0;
i < package->GetPropertyCount();
i++)
{
props::IProperty* property =
package->GetProperty(i);
const TCHAR* name =
property->GetName();
props::FOURCHARCODE id =
property->GetID();
props::IProperty::PROPERTY_TYPE type =
property->GetType();
props::IProperty::PROPERTY_ASPECT aspect =
property->GetAspect();
// Decide what to do based on what
// the property says about itself.
}The consuming code doesn't ask:
"Is this a packaging configuration?"
It asks:
"What properties are here?"
That is the relationship PowerProps is intended to create.
Without a common property system:
┌── Editor-specific interface
Application ────┼── Serializer-specific interface
State ├── Plugin-specific interface
├── Scripting-specific interface
└── Configuration-specific interface
Every new consumer needs another way into the application.
With PowerProps:
┌── Editor
├── Serializer
Application ──► ├── Plugin
Property Set ├── Configuration
├── Scripting
├── Diagnostics
└── Whatever comes next
The producer describes its state once.
Consumers discover what is available.
This is where PowerProps becomes particularly useful.
Suppose you write a property editor today.
Next year, you add an entirely new subsystem containing properties that didn't exist when the editor was written.
If the editor understands those property types and aspects, it may require no changes at all.
The new state is simply discovered at runtime.
The same applies to serializers, plugin systems, scripting layers, diagnostic tools, and other generic consumers.
That's the point of PowerProps.
Not merely accessing values.
Discovering capabilities that weren't known when the consuming code was written.
PowerProps flags and aspects provide hints that generic user interfaces can use without restricting what application code itself is allowed to do.
A property can indicate that it is:
- required
- read-only
- hidden
- intended for a tooltip
- type-locked
- aspect-locked
Aspects can communicate richer meaning such as:
- filename or directory
- color
- percentage
- date or time
- latitude / longitude
- rotation
- dimensions
- matrices
- eye or lighting information
- boolean presentation such as Yes/No, On/Off, or Enabled/Disabled
The property doesn't dictate how a UI must look.
It simply provides enough meaning for a generic UI to make an informed choice.
Properties can also represent enumerated values.
Enumeration strings can be provided directly:
props::IProperty* quality =
properties->CreateProperty(
_T("Quality"),
'QUAL');
quality->SetEnumStrings(
_T("Low,Medium,High,Ultra"));
quality->SetEnumVal(2);Or an application can implement IEnumProvider when the available choices need to be determined dynamically.
The consumer still sees a property.
The source of its choices is an implementation detail.
A property set can serialize itself without requiring every consumer to understand the data that produced it.
Binary serialization supports several levels of description:
Values Only
ID, type, value
Terse
ID, type, aspect, value
Verbose
Name, ID, type, aspect, value
For example:
size_t required = 0;
package->Serialize(
props::IProperty::SM_BIN_VERBOSE,
nullptr,
0,
&required);The property set can also serialize to and deserialize from XML.
This means the same property description used for editors and plugins can also serve as the basis for persistence or interchange.
A system that needs to react when properties change can implement IPropertyChangeListener:
class PropertyListener :
public props::IPropertyChangeListener
{
public:
void PropertyChanged(
const props::IProperty* property) override
{
// React to the change.
}
};and register it with the set:
PropertyListener listener;
properties->SetChangeListener(
&listener);The provider can expose its state while interested systems react to changes through the same property model.
PowerProps also includes a handful of lightweight utility types that make it easier for unrelated systems to agree on common kinds of data.
For example:
props::TFlags64 flags;
props::TVec3F position;
props::TVec4F color;
props::TMat4x4F transform;The flag types provide convenient bitfield operations:
props::TFlags64 flags;
flags.Set(0x04);
flags.Set(0x10);
if (flags.IsSet(0x04))
{
// ...
}
flags.Clear(0x04);Vector types provide common 2D, 3D, and 4D representations:
props::TVec3F position(10.0f, 20.0f, 30.0f);
float x = position.x;
float y = position.y;
float z = position.z;Some types also provide useful aliases for the same underlying data:
props::TVec3F color;
color.r = 1.0f;
color.g = 0.5f;
color.b = 0.25f;or:
props::TVec2F location;
location.lon = -81.2f;
location.lat = 28.5f;PowerProps also includes 3x3 and 4x4 floating-point matrix types for values that need to move through the property system without depending on a particular math library.
These helpers are intentionally small. They provide a common vocabulary for property data without forcing the rest of an application to adopt a larger framework.
PowerProps favors practical abstractions over clever ones.
There is no code generation.
No language extension.
No compiler magic.
No requirement that application state be reorganized around the property system.
Your data remains your data.
Your C++ remains ordinary C++.
PowerProps provides a small collection of interfaces and a common vocabulary through which otherwise independent parts of an application can understand one another.
There is an old belief that names have power.
A value without a name is merely data.
Give it a name, a type, an identity, and meaning, and other systems can begin to reason about it.
Editors can edit it.
Serializers can preserve it.
Plugins can discover it.
Tools that didn't exist when the property was written can understand it.
The data itself hasn't changed.
It has simply learned how to introduce itself.
When a property set is no longer needed:
package->Release();The property set owns the ordinary properties created through it and manages their lifetime.
MIT License.
Use it freely.
Build useful things.
Build impossible things.