diff --git a/src/Framework/Framework/Binding/ActiveDotvvmProperty.cs b/src/Framework/Framework/Binding/ActiveDotvvmProperty.cs
index 831b85d787..7d87b9982e 100644
--- a/src/Framework/Framework/Binding/ActiveDotvvmProperty.cs
+++ b/src/Framework/Framework/Binding/ActiveDotvvmProperty.cs
@@ -13,6 +13,10 @@ namespace DotVVM.Framework.Binding
/// An abstract DotvvmProperty which contains code to be executed when the assigned control is being rendered.
public abstract class ActiveDotvvmProperty : DotvvmProperty
{
+ internal ActiveDotvvmProperty(string name, Type declaringType, bool isValueInherited) : base(name, declaringType, isValueInherited)
+ {
+ }
+
public abstract void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context, DotvvmControl control);
diff --git a/src/Framework/Framework/Binding/BindingHelper.cs b/src/Framework/Framework/Binding/BindingHelper.cs
index fd2fb2264d..2cdbbca809 100644
--- a/src/Framework/Framework/Binding/BindingHelper.cs
+++ b/src/Framework/Framework/Binding/BindingHelper.cs
@@ -64,8 +64,8 @@ public static string FormatKnockoutScript(this ParametrizedCode code, DotvvmBind
/// Gets Internal.PathFragmentProperty or DataContext.KnockoutExpression. Returns null if none of these is set.
///
public static string? GetDataContextPathFragment(this DotvvmBindableObject currentControl) =>
- currentControl.properties.TryGet(Internal.PathFragmentProperty, out var pathFragment) && pathFragment is string pathFragmentStr ? pathFragmentStr :
- currentControl.properties.TryGet(DotvvmBindableObject.DataContextProperty, out var dataContext) && dataContext is IValueBinding binding ?
+ currentControl.properties.TryGet(DotvvmPropertyIdAssignment.PropertyIds.Internal_PathFragment, out var pathFragment) && pathFragment is string pathFragmentStr ? pathFragmentStr :
+ currentControl.properties.TryGet(DotvvmPropertyIdAssignment.PropertyIds.DotvvmBindableObject_DataContext, out var dataContext) && dataContext is IValueBinding binding ?
binding.GetProperty()
.Code.FormatKnockoutScript(currentControl, binding) :
null;
@@ -88,16 +88,16 @@ internal static (int stepsUp, DotvvmBindableObject target) FindDataContextTarget
if (bindingContext == null || controlContext == null || controlContext.Equals(bindingContext)) return (0, control);
var changes = 0;
- foreach (var a in control.GetAllAncestors(includingThis: true))
+ for (var ancestor = control; ancestor is {}; ancestor = ancestor.Parent)
{
- var ancestorContext = a.GetDataContextType(inherit: false);
+ var ancestorContext = ancestor.GetDataContextType(inherit: false);
if (bindingContext.Equals(ancestorContext))
- return (changes, a);
+ return (changes, ancestor);
// count only client-side data contexts (DataContext={resource:} is skipped in JS)
- if (a.properties.TryGet(DotvvmBindableObject.DataContextProperty, out var ancestorRuntimeContext))
+ if (ancestor.properties.TryGet(DotvvmPropertyIdAssignment.PropertyIds.DotvvmBindableObject_DataContext, out var ancestorRuntimeContext))
{
- if (a.properties.TryGet(Internal.IsServerOnlyDataContextProperty, out var isServerOnly) && isServerOnly != null)
+ if (ancestor.properties.GetOrNull(DotvvmPropertyIdAssignment.PropertyIds.Internal_IsServerOnlyDataContext) is {} isServerOnly)
{
if (isServerOnly is false)
changes++;
@@ -173,9 +173,8 @@ public static T ExecDelegate(this BindingDelegate func, DotvvmBindableObje
// this has O(h^2) complexity because GetValue calls another GetDataContexts,
// but this function is used rarely - for exceptions, manually created bindings, ...
// Normal bindings have specialized code generated in BindingCompiler
- if (c.IsPropertySet(DotvvmBindableObject.DataContextProperty, inherit: false))
+ if (c.properties.Contains(DotvvmPropertyIdAssignment.PropertyIds.DotvvmBindableObject_DataContext))
{
- Debug.Assert(c.properties.Contains(DotvvmBindableObject.DataContextProperty), "Control claims that DataContextProperty is set, but it's not present in the properties dictionary.");
yield return c.GetValue(DotvvmBindableObject.DataContextProperty);
count--;
}
diff --git a/src/Framework/Framework/Binding/CompileTimeOnlyDotvvmProperty.cs b/src/Framework/Framework/Binding/CompileTimeOnlyDotvvmProperty.cs
index b982ee6569..971ca845d8 100644
--- a/src/Framework/Framework/Binding/CompileTimeOnlyDotvvmProperty.cs
+++ b/src/Framework/Framework/Binding/CompileTimeOnlyDotvvmProperty.cs
@@ -11,7 +11,7 @@ namespace DotVVM.Framework.Binding
///
public class CompileTimeOnlyDotvvmProperty : DotvvmProperty
{
- public CompileTimeOnlyDotvvmProperty()
+ private CompileTimeOnlyDotvvmProperty(string name, Type declaringType) : base(name, declaringType, isValueInherited: false)
{
}
@@ -37,7 +37,7 @@ public override bool IsSet(DotvvmBindableObject control, bool inherit = true)
///
public static CompileTimeOnlyDotvvmProperty Register(string propertyName)
{
- var property = new CompileTimeOnlyDotvvmProperty();
+ var property = new CompileTimeOnlyDotvvmProperty(propertyName, typeof(TDeclaringType));
return (CompileTimeOnlyDotvvmProperty)Register(propertyName, property: property);
}
}
diff --git a/src/Framework/Framework/Binding/DelegateActionProperty.cs b/src/Framework/Framework/Binding/DelegateActionProperty.cs
index 81c1e23c86..3bb96daef1 100644
--- a/src/Framework/Framework/Binding/DelegateActionProperty.cs
+++ b/src/Framework/Framework/Binding/DelegateActionProperty.cs
@@ -13,9 +13,9 @@ namespace DotVVM.Framework.Binding
/// DotvvmProperty which calls the function passed in the Register method, when the assigned control is being rendered.
public sealed class DelegateActionProperty: ActiveDotvvmProperty
{
- private Action func;
+ private readonly Action func;
- public DelegateActionProperty(Action func)
+ public DelegateActionProperty(Action func, string name, Type declaringType, bool isValueInherited) : base(name, declaringType, isValueInherited)
{
this.func = func;
}
@@ -27,7 +27,8 @@ public override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestCon
public static DelegateActionProperty Register(string name, Action func, [AllowNull] TValue defaultValue = default(TValue))
{
- return (DelegateActionProperty)DotvvmProperty.Register(name, defaultValue, false, new DelegateActionProperty(func));
+ var property = new DelegateActionProperty(func, name, typeof(TDeclaringType), isValueInherited: false);
+ return (DelegateActionProperty)DotvvmProperty.Register(name, defaultValue, false, property);
}
}
diff --git a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.CodeGeneration.cs b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.CodeGeneration.cs
index a0b7500aad..3c8fad160d 100644
--- a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.CodeGeneration.cs
+++ b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.CodeGeneration.cs
@@ -76,27 +76,30 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyG
var valueParameter = Expression.Parameter(type, "value");
var ctor = typeof(VirtualPropertyGroupDictionary<>)
.MakeGenericType(propType)
- .GetConstructor(new [] { typeof(DotvvmBindableObject), typeof(DotvvmPropertyGroup) })!;
+ .GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, [ typeof(DotvvmBindableObject), typeof(ushort), typeof(bool) ], null)!;
var createMethod = typeof(VirtualPropertyGroupDictionary<>)
.MakeGenericType(propType)
.GetMethod(
typeof(ValueOrBinding).IsAssignableFrom(elementType) ? nameof(VirtualPropertyGroupDictionary.CreatePropertyDictionary) :
nameof(VirtualPropertyGroupDictionary.CreateValueDictionary),
- BindingFlags.Public | BindingFlags.Static
+ BindingFlags.NonPublic | BindingFlags.Static,
+ binder: null,
+ [ typeof(DotvvmBindableObject), typeof(ushort) ],
+ modifiers: null
)!;
var enumerableType = typeof(IEnumerable<>).MakeGenericType(typeof(KeyValuePair<,>).MakeGenericType(typeof(string), elementType));
var copyFromMethod =
typeof(VirtualPropertyGroupDictionary<>)
.MakeGenericType(propType)
- .GetMethod("CopyFrom", new [] { enumerableType, typeof(bool) })!;
+ .GetMethod("CopyFrom", [ enumerableType, typeof(bool) ])!;
return (
Lambda(
- Convert(Call(createMethod, currentControlParameter, Constant(pgroup)), type),
+ Convert(Call(createMethod, currentControlParameter, Constant(pgroup.Id)), type),
currentControlParameter
),
Lambda(
Call(
- New(ctor, currentControlParameter, Constant(pgroup)),
+ New(ctor, currentControlParameter, Constant(pgroup.Id), Constant(pgroup.IsBindingProperty)),
copyFromMethod,
Convert(valueParameter, enumerableType),
Constant(true) // clear
@@ -105,8 +108,10 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyG
valueParameter
)
);
-
}
+
+ static readonly ConstructorInfo DotvvmPropertyIdConstructor = typeof(DotvvmPropertyId).GetConstructor(new [] { typeof(uint) }).NotNull();
+
public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyAccessors(Type type, DotvvmProperty property)
{
if (property is DotvvmPropertyAlias propertyAlias)
@@ -114,27 +119,31 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
// if the property does not override GetValue/SetValue, we'll use
// control.properties dictionary directly to avoid virtual method calls
- var canUseDirectAccess =
- !property.IsValueInherited && (
- property.GetType() == typeof(DotvvmProperty) ||
- property.GetType().GetMethod(nameof(DotvvmProperty.GetValue), new [] { typeof(DotvvmBindableObject), typeof(bool) })!.DeclaringType == typeof(DotvvmProperty) &&
- property.GetType().GetMethod(nameof(DotvvmProperty.SetValue), new [] { typeof(DotvvmBindableObject), typeof(object) })!.DeclaringType == typeof(DotvvmProperty));
+ var canUseDirectAccess = !property.IsValueInherited && DotvvmPropertyIdAssignment.TypeCanUseAnyDirectAccess(property.GetType());
var valueParameter = Expression.Parameter(type, "value");
var unwrappedType = type.UnwrapNullableType();
+ var defaultObj = TypeConversion.BoxToObject(Constant(property.DefaultValue));
+ // try to access the readonly static field, as .NET can optimize that better than whatever Linq.Expression Constant compiles to
+ var propertyExpr =
+ property.AttributeProvider is FieldInfo field && field.IsStatic && field.IsInitOnly && field.GetValue(null) == property
+ ? Field(null, field)
+ : (Expression)Constant(property);
+ var propertyIdExpr = New(DotvvmPropertyIdConstructor, Constant(property.Id.Id, typeof(uint)));
+
var boxedValueParameter = TypeConversion.BoxToObject(valueParameter);
var setValueRaw =
canUseDirectAccess
- ? Call(typeof(Helpers), nameof(Helpers.SetValueDirect), Type.EmptyTypes, currentControlParameter, Constant(property), boxedValueParameter)
- : Call(currentControlParameter, nameof(DotvvmBindableObject.SetValueRaw), Type.EmptyTypes, Constant(property), boxedValueParameter);
+ ? Call(typeof(Helpers), nameof(Helpers.SetValueDirect), Type.EmptyTypes, currentControlParameter, propertyIdExpr, defaultObj, boxedValueParameter)
+ : Call(currentControlParameter, nameof(DotvvmBindableObject.SetValueRaw), Type.EmptyTypes, propertyExpr, boxedValueParameter);
if (typeof(IBinding).IsAssignableFrom(type))
{
var getValueRaw =
canUseDirectAccess
- ? Call(typeof(Helpers), nameof(Helpers.GetValueRawDirect), Type.EmptyTypes, currentControlParameter, Constant(property))
- : Call(currentControlParameter, nameof(DotvvmBindableObject.GetValueRaw), Type.EmptyTypes, Constant(property), Constant(property.IsValueInherited));
+ ? Call(typeof(Helpers), nameof(Helpers.GetValueRawDirect), Type.EmptyTypes, currentControlParameter, propertyIdExpr, defaultObj)
+ : Call(currentControlParameter, nameof(DotvvmBindableObject.GetValueRaw), Type.EmptyTypes, propertyExpr, Constant(property.IsValueInherited));
return (
Lambda(
Convert(getValueRaw, type),
@@ -173,11 +182,17 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
Expression.Call(
getValueOrBindingMethod,
currentControlParameter,
- Constant(property)),
+ canUseDirectAccess ? propertyIdExpr : propertyExpr,
+ defaultObj),
currentControlParameter
),
Expression.Lambda(
- Expression.Call(setValueOrBindingMethod, currentControlParameter, Expression.Constant(property), valueParameter),
+ Expression.Call(
+ setValueOrBindingMethod,
+ currentControlParameter,
+ canUseDirectAccess ? propertyIdExpr : propertyExpr,
+ defaultObj,
+ valueParameter),
currentControlParameter, valueParameter
)
);
@@ -191,13 +206,13 @@ public static (LambdaExpression getter, LambdaExpression setter) CreatePropertyA
Expression getValue;
if (canUseDirectAccess && unwrappedType.IsValueType)
{
- getValue = Call(typeof(Helpers), nameof(Helpers.GetStructValueDirect), new Type[] { unwrappedType }, currentControlParameter, Constant(property));
+ getValue = Call(typeof(Helpers), nameof(Helpers.GetStructValueDirect), [ unwrappedType ], currentControlParameter, propertyIdExpr, Constant(property.DefaultValue, type.MakeNullableType()));
if (!type.IsNullable())
getValue = Expression.Property(getValue, "Value");
}
else
{
- getValue = Call(currentControlParameter, getValueMethod, Constant(property), Constant(property.IsValueInherited));
+ getValue = Call(currentControlParameter, getValueMethod, propertyExpr, Constant(property.IsValueInherited));
getValue = Convert(getValue, type);
}
return (
diff --git a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.Helpers.cs b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.Helpers.cs
index a276c1b989..0375742a28 100644
--- a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.Helpers.cs
+++ b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.Helpers.cs
@@ -12,7 +12,7 @@ public partial class DotvvmCapabilityProperty
{
internal static class Helpers
{
- public static ValueOrBinding? GetOptionalValueOrBinding(DotvvmBindableObject c, DotvvmProperty p)
+ public static ValueOrBinding? GetOptionalValueOrBinding(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue)
{
if (c.properties.TryGet(p, out var x))
{
@@ -25,13 +25,13 @@ internal static class Helpers
}
else return null;
}
- public static ValueOrBinding GetValueOrBinding(DotvvmBindableObject c, DotvvmProperty p)
+ public static ValueOrBinding GetValueOrBinding(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue)
{
if (!c.properties.TryGet(p, out var x))
- x = p.DefaultValue;
+ x = defaultValue;
return ValueOrBinding.FromBoxedValue(x);
}
- public static ValueOrBinding? GetOptionalValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p)
+ public static ValueOrBinding? GetOptionalValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue)
{
if (c.IsPropertySet(p))
{
@@ -45,42 +45,42 @@ public static ValueOrBinding GetValueOrBinding(DotvvmBindableObject c, Dot
}
else return null;
}
- public static ValueOrBinding GetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p)
+ public static ValueOrBinding GetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue)
{
return ValueOrBinding.FromBoxedValue(c.GetValue(p));
}
- public static void SetOptionalValueOrBinding(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding? val)
+ public static void SetOptionalValueOrBinding(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, ValueOrBinding? val)
{
if (val.HasValue)
{
- SetValueOrBinding(c, p, val.GetValueOrDefault());
+ SetValueOrBinding(c, p, defaultValue, val.GetValueOrDefault());
}
else
{
c.properties.Remove(p);
}
}
- public static void SetValueOrBinding(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding val)
+ public static void SetValueOrBinding(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, ValueOrBinding val)
{
var boxedVal = val.UnwrapToObject();
- SetValueDirect(c, p, boxedVal);
+ SetValueDirect(c, p, defaultValue, boxedVal);
}
- public static void SetOptionalValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding? val)
+ public static void SetOptionalValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue, ValueOrBinding? val)
{
if (val.HasValue)
{
- SetValueOrBindingSlow(c, p, val.GetValueOrDefault());
+ SetValueOrBindingSlow(c, p, defaultValue, val.GetValueOrDefault());
}
else
{
- c.SetValue(p, p.DefaultValue); // set to default value, just in case this property is backed in a different place than c.properties[p]
+ c.SetValue(p, defaultValue); // set to default value, just in case this property is backed in a different place than c.properties[p]
c.properties.Remove(p);
}
}
- public static void SetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, ValueOrBinding val)
+ public static void SetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProperty p, object? defaultValue, ValueOrBinding val)
{
var boxedVal = val.UnwrapToObject();
- if (Object.Equals(boxedVal, p.DefaultValue) && !c.IsPropertySet(p))
+ if (Object.Equals(boxedVal, defaultValue) && !c.IsPropertySet(p))
{
// setting to default value and the property is not set -> do nothing
}
@@ -90,15 +90,15 @@ public static void SetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProper
}
}
- public static object? GetValueRawDirect(DotvvmBindableObject c, DotvvmProperty p)
+ public static object? GetValueRawDirect(DotvvmBindableObject c, DotvvmPropertyId p, object defaultValue)
{
if (c.properties.TryGet(p, out var x))
{
return x;
}
- else return p.DefaultValue;
+ else return defaultValue;
}
- public static T? GetStructValueDirect(DotvvmBindableObject c, DotvvmProperty p)
+ public static T? GetStructValueDirect(DotvvmBindableObject c, DotvvmPropertyId p, T? defaultValue)
where T: struct
{
// T being a struct allows us to invert the rather expensive `is IBinding` typecheck in EvalPropertyValue
@@ -111,11 +111,11 @@ public static void SetValueOrBindingSlow(DotvvmBindableObject c, DotvvmProper
return xValue;
return (T?)c.EvalPropertyValue(p, x);
}
- else return (T?)p.DefaultValue;
+ else return defaultValue;
}
- public static void SetValueDirect(DotvvmBindableObject c, DotvvmProperty p, object? value)
+ public static void SetValueDirect(DotvvmBindableObject c, DotvvmPropertyId p, object? defaultValue, object? value)
{
- if (Object.Equals(p.DefaultValue, value) && !c.properties.Contains(p))
+ if (Object.Equals(defaultValue, value) && !c.properties.Contains(p))
{
// setting to default value and the property is not set -> do nothing
}
diff --git a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.cs b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.cs
index c6846b28af..dfcb6b23fd 100644
--- a/src/Framework/Framework/Binding/DotvvmCapabilityProperty.cs
+++ b/src/Framework/Framework/Binding/DotvvmCapabilityProperty.cs
@@ -45,13 +45,9 @@ private DotvvmCapabilityProperty(
Type declaringType,
ICustomAttributeProvider? attributeProvider,
DotvvmCapabilityProperty? declaringCapability
- ): base()
+ ): base(name ?? prefix + type.Name, declaringType, isValueInherited: false)
{
- name ??= prefix + type.Name;
-
- this.Name = name;
this.PropertyType = type;
- this.DeclaringType = declaringType;
this.Prefix = prefix;
this.AddUsedInCapability(declaringCapability);
@@ -63,14 +59,15 @@ private DotvvmCapabilityProperty(
AssertPropertyNotDefined(this, postContent: false);
- var dotnetFieldName = ToPascalCase(name.Replace("-", "_").Replace(":", "_"));
+ var dotnetFieldName = ToPascalCase(Name.Replace("-", "_").Replace(":", "_"));
attributeProvider ??=
declaringType.GetProperty(dotnetFieldName) ??
declaringType.GetField(dotnetFieldName) ??
(ICustomAttributeProvider?)declaringType.GetField(dotnetFieldName + "Property") ??
- throw new Exception($"Capability backing field could not be found and capabilityAttributeProvider argument was not provided. Property: {declaringType.Name}.{name}. Please declare a field or property named {dotnetFieldName}.");
+ throw new Exception($"Capability backing field could not be found and capabilityAttributeProvider argument was not provided. Property: {declaringType.Name}.{Name}. Please declare a field or property named {dotnetFieldName}.");
DotvvmProperty.InitializeProperty(this, attributeProvider);
+ this.MarkupOptions._mappingMode ??= MappingMode.Exclude;
}
public override object GetValue(DotvvmBindableObject control, bool inherit = true) => Getter(control);
@@ -200,15 +197,8 @@ static DotvvmCapabilityProperty RegisterCapability(DotvvmCapabilityProperty prop
{
var declaringType = property.DeclaringType.NotNull();
var capabilityType = property.PropertyType.NotNull();
- var name = property.Name.NotNull();
AssertPropertyNotDefined(property);
- var attributes = new CustomAttributesProvider(
- new MarkupOptionsAttribute
- {
- MappingMode = MappingMode.Exclude
- }
- );
- DotvvmProperty.Register(name, capabilityType, declaringType, DBNull.Value, false, property, attributes);
+ DotvvmProperty.Register(property);
if (!capabilityRegistry.TryAdd((declaringType, capabilityType, property.Prefix), property))
throw new($"unhandled naming conflict when registering capability {capabilityType}.");
capabilityListRegistry.AddOrUpdate(
diff --git a/src/Framework/Framework/Binding/DotvvmProperty.cs b/src/Framework/Framework/Binding/DotvvmProperty.cs
index 5bdfc5ef21..d394204fe0 100644
--- a/src/Framework/Framework/Binding/DotvvmProperty.cs
+++ b/src/Framework/Framework/Binding/DotvvmProperty.cs
@@ -25,11 +25,13 @@ namespace DotVVM.Framework.Binding
[DebuggerDisplay("{FullName}")]
public class DotvvmProperty : IPropertyDescriptor
{
+ public DotvvmPropertyId Id { get; }
///
/// Gets or sets the name of the property.
///
- public string Name { get; protected set; }
+ public string Name { get; }
+
[JsonIgnore]
ITypeDescriptor IControlAttributeDescriptor.DeclaringType => new ResolvedTypeDescriptor(DeclaringType);
@@ -50,7 +52,7 @@ public class DotvvmProperty : IPropertyDescriptor
///
/// Gets the type of the class where the property is registered.
///
- public Type DeclaringType { get; protected set; }
+ public Type DeclaringType { get; }
///
/// Gets whether the value can be inherited from the parent controls.
@@ -61,17 +63,17 @@ public class DotvvmProperty : IPropertyDescriptor
/// Gets or sets the Reflection property information.
///
[JsonIgnore]
- public PropertyInfo? PropertyInfo { get; private set; }
+ public PropertyInfo? PropertyInfo { get; protected set; }
///
/// Provider of custom attributes for this property.
///
- internal ICustomAttributeProvider AttributeProvider { get; set; }
+ internal ICustomAttributeProvider AttributeProvider { get; private protected set; }
///
/// Gets or sets the markup options.
///
- public MarkupOptionsAttribute MarkupOptions { get; set; }
+ public MarkupOptionsAttribute MarkupOptions { get; protected set; }
///
/// Determines if property type inherits from IBinding
@@ -109,6 +111,8 @@ public string FullName
IPropertyDescriptor? IControlAttributeDescriptor.OwningCapability => OwningCapability;
IEnumerable IControlAttributeDescriptor.UsedInCapabilities => UsedInCapabilities;
+ private bool initialized = false;
+
internal void AddUsedInCapability(DotvvmCapabilityProperty? p)
{
@@ -123,12 +127,24 @@ internal void AddUsedInCapability(DotvvmCapabilityProperty? p)
}
}
- ///
- /// Prevents a default instance of the class from being created.
- ///
#pragma warning disable CS8618 // DotvvmProperty is usually initialized by InitializeProperty
- internal DotvvmProperty()
+ internal DotvvmProperty(string name, Type declaringType, bool isValueInherited)
{
+ if (name is null) throw new ArgumentNullException(nameof(name));
+ if (declaringType is null) throw new ArgumentNullException(nameof(declaringType));
+ this.Name = name;
+ this.DeclaringType = declaringType;
+ this.IsValueInherited = isValueInherited;
+ this.Id = DotvvmPropertyIdAssignment.RegisterProperty(this);
+ }
+ internal DotvvmProperty(DotvvmPropertyId id, string name, Type declaringType)
+ {
+ if (name is null) throw new ArgumentNullException(nameof(name));
+ if (declaringType is null) throw new ArgumentNullException(nameof(declaringType));
+ if (id.Id == 0) throw new ArgumentException("DotvvmProperty must have an ID", nameof(id));
+ this.Name = name;
+ this.DeclaringType = declaringType;
+ this.Id = id;
}
internal DotvvmProperty(
#pragma warning restore CS8618
@@ -137,7 +153,8 @@ internal DotvvmProperty(
Type declaringType,
object? defaultValue,
bool isValueInherited,
- ICustomAttributeProvider attributeProvider
+ ICustomAttributeProvider attributeProvider,
+ DotvvmPropertyId id = default
)
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
@@ -146,6 +163,9 @@ ICustomAttributeProvider attributeProvider
this.DefaultValue = defaultValue;
this.IsValueInherited = isValueInherited;
this.AttributeProvider = attributeProvider ?? throw new ArgumentNullException(nameof(attributeProvider));
+ if (id.Id == 0)
+ id = DotvvmPropertyIdAssignment.RegisterProperty(this);
+ this.Id = id;
InitializeProperty(this);
}
@@ -162,6 +182,23 @@ public T[] GetAttributes()
return attrA.Concat(attrB).ToArray();
}
+ public T? GetAttribute() where T: Attribute
+ {
+ var t = typeof(T);
+ var provider = AttributeProvider;
+ if (provider.IsDefined(t, true))
+ {
+ return (T)provider.GetCustomAttributes(t, true).Single();
+ }
+ var property = PropertyInfo;
+ if (property is {} && !object.ReferenceEquals(property, provider))
+ {
+ return (T?)property.GetCustomAttribute(t, true);
+ }
+
+ return null;
+ }
+
public bool IsOwnedByCapability(Type capability) =>
(this is DotvvmCapabilityProperty && this.PropertyType == capability) ||
OwningCapability?.IsOwnedByCapability(capability) == true;
@@ -172,9 +209,10 @@ public bool IsOwnedByCapability(DotvvmCapabilityProperty capability) =>
private object? GetInheritedValue(DotvvmBindableObject control)
{
+ var id = this.Id;
for (var p = control.Parent; p is not null; p = p.Parent)
{
- if (p.properties.TryGet(this, out var v))
+ if (p.properties.TryGet(id, out var v))
return v;
}
return DefaultValue;
@@ -185,7 +223,7 @@ public bool IsOwnedByCapability(DotvvmCapabilityProperty capability) =>
///
public virtual object? GetValue(DotvvmBindableObject control, bool inherit = true)
{
- if (control.properties.TryGet(this, out var value))
+ if (control.properties.TryGet(Id, out var value))
{
return value;
}
@@ -196,20 +234,29 @@ public bool IsOwnedByCapability(DotvvmCapabilityProperty capability) =>
return DefaultValue;
}
+ private bool IsSetInherited(DotvvmBindableObject control)
+ {
+ for (var p = control.Parent; p is not null; p = p.Parent)
+ {
+ if (p.properties.Contains(Id))
+ return true;
+ }
+ return false;
+ }
///
/// Gets whether the value of the property is set
///
public virtual bool IsSet(DotvvmBindableObject control, bool inherit = true)
{
- if (control.properties.Contains(this))
+ if (control.properties.Contains(Id))
{
return true;
}
- if (IsValueInherited && inherit && control.Parent != null)
+ if (IsValueInherited && inherit)
{
- return IsSet(control.Parent);
+ return IsSetInherited(control);
}
return false;
@@ -221,7 +268,7 @@ public virtual bool IsSet(DotvvmBindableObject control, bool inherit = true)
///
public virtual void SetValue(DotvvmBindableObject control, object? value)
{
- control.properties.Set(this, value);
+ control.properties.Set(Id, value);
}
///
@@ -258,14 +305,32 @@ public virtual void SetValue(DotvvmBindableObject control, object? value)
public static DotvvmProperty Register(string propertyName, Type propertyType, Type declaringType, object? defaultValue, bool isValueInherited, DotvvmProperty? property, ICustomAttributeProvider attributeProvider, bool throwOnDuplicateRegistration = true)
{
- if (property == null) property = new DotvvmProperty();
+ if (propertyName is null) throw new ArgumentNullException(nameof(propertyName));
+ if (propertyType is null) throw new ArgumentNullException(nameof(propertyType));
+ if (declaringType is null) throw new ArgumentNullException(nameof(declaringType));
+ if (attributeProvider is null) throw new ArgumentNullException(nameof(attributeProvider));
- property.Name = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
- property.IsValueInherited = isValueInherited;
- property.DeclaringType = declaringType ?? throw new ArgumentNullException(nameof(declaringType));
- property.PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType));
- property.DefaultValue = defaultValue;
- property.AttributeProvider = attributeProvider ?? throw new ArgumentNullException(nameof(attributeProvider));
+ if (property == null)
+ {
+ property = new DotvvmProperty(propertyName, propertyType, declaringType, defaultValue, isValueInherited, attributeProvider);
+ }
+ else
+ {
+ if (!property.initialized)
+ {
+ property.PropertyType = propertyType;
+ property.DefaultValue = defaultValue;
+ property.IsValueInherited = isValueInherited;
+ property.AttributeProvider = attributeProvider;
+ InitializeProperty(property, attributeProvider);
+ }
+ if (property.Name != propertyName) throw new ArgumentException("The property name does not match the existing property.", nameof(propertyName));
+ if (property.IsValueInherited != isValueInherited) throw new ArgumentException("The IsValueInherited does not match the existing property.", nameof(isValueInherited));
+ if (property.DeclaringType != declaringType) throw new ArgumentException("The declaring type does not match the existing property.", nameof(declaringType));
+ if (property.PropertyType != propertyType) throw new ArgumentException("The property type does not match the existing property.", nameof(propertyType));
+ if (property.DefaultValue != defaultValue) throw new ArgumentException("The default value does not match the existing property.", nameof(defaultValue));
+ if (property.AttributeProvider != attributeProvider) throw new ArgumentException("The attribute provider does not match the existing property.", nameof(attributeProvider));
+ }
return Register(property, throwOnDuplicateRegistration);
}
@@ -288,7 +353,11 @@ public override string Message { get {
internal static DotvvmProperty Register(DotvvmProperty property, bool throwOnDuplicateRegistration = true)
{
- InitializeProperty(property);
+ if (property.Id.Id == 0)
+ throw new Exception("DotvvmProperty must have an ID");
+
+ if (!property.initialized)
+ throw new Exception("DotvvmProperty must be initialized before registration.");
var key = (property.DeclaringType, property.Name);
if (!registeredProperties.TryAdd(key, property))
@@ -373,8 +442,8 @@ public static DotvvmPropertyAlias RegisterAlias(
aliasName,
declaringType,
aliasAttribute.AliasedPropertyName,
- aliasAttribute.AliasedPropertyDeclaringType ?? declaringType);
- propertyAlias.AttributeProvider = attributeProvider;
+ aliasAttribute.AliasedPropertyDeclaringType ?? declaringType,
+ attributeProvider);
propertyAlias.ObsoleteAttribute = attributeProvider.GetCustomAttribute();
var key = (propertyAlias.DeclaringType, propertyAlias.Name);
@@ -396,6 +465,8 @@ public static DotvvmPropertyAlias RegisterAlias(
public static void InitializeProperty(DotvvmProperty property, ICustomAttributeProvider? attributeProvider = null)
{
+ if (property.initialized)
+ throw new Exception("DotvvmProperty should not be initialized twice.");
if (string.IsNullOrWhiteSpace(property.Name))
throw new Exception("DotvvmProperty must not have empty name.");
if (property.DeclaringType is null || property.PropertyType is null)
@@ -409,25 +480,27 @@ public static void InitializeProperty(DotvvmProperty property, ICustomAttributeP
property.PropertyInfo ??
throw new ArgumentNullException(nameof(attributeProvider));
property.MarkupOptions ??=
- property.GetAttributes().SingleOrDefault()
+ property.GetAttribute()
?? new MarkupOptionsAttribute();
if (string.IsNullOrEmpty(property.MarkupOptions.Name))
property.MarkupOptions.Name = property.Name;
property.DataContextChangeAttributes ??=
- property.GetAttributes().ToArray();
+ property.GetAttributes();
property.DataContextManipulationAttribute ??=
- property.GetAttributes().SingleOrDefault();
- if (property.DataContextManipulationAttribute != null && property.DataContextChangeAttributes.Any())
+ property.GetAttribute();
+ if (property.DataContextManipulationAttribute != null && property.DataContextChangeAttributes.Length != 0)
throw new ArgumentException($"{nameof(DataContextChangeAttributes)} and {nameof(DataContextManipulationAttribute)} cannot be set both at property '{property.FullName}'.");
property.IsBindingProperty = typeof(IBinding).IsAssignableFrom(property.PropertyType);
- property.ObsoleteAttribute = property.AttributeProvider.GetCustomAttribute();
+ property.ObsoleteAttribute = property.GetAttribute();
if (property.IsBindingProperty)
{
property.MarkupOptions.AllowHardCodedValue = false;
property.MarkupOptions.AllowResourceBinding = !typeof(IValueBinding).IsAssignableFrom(property.PropertyType);
}
+
+ property.initialized = true;
}
public static void CheckAllPropertiesAreRegistered(Type controlType)
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyAlias.cs b/src/Framework/Framework/Binding/DotvvmPropertyAlias.cs
index dca78455b9..1adb9deeee 100644
--- a/src/Framework/Framework/Binding/DotvvmPropertyAlias.cs
+++ b/src/Framework/Framework/Binding/DotvvmPropertyAlias.cs
@@ -12,10 +12,9 @@ public DotvvmPropertyAlias(
string aliasName,
Type declaringType,
string aliasedPropertyName,
- Type aliasedPropertyDeclaringType)
+ Type aliasedPropertyDeclaringType,
+ System.Reflection.ICustomAttributeProvider attributeProvider): base(aliasName, declaringType, isValueInherited: false)
{
- Name = aliasName;
- DeclaringType = declaringType;
AliasedPropertyName = aliasedPropertyName;
AliasedPropertyDeclaringType = aliasedPropertyDeclaringType;
MarkupOptions = new MarkupOptionsAttribute();
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyId.cs b/src/Framework/Framework/Binding/DotvvmPropertyId.cs
new file mode 100644
index 0000000000..f7c765899a
--- /dev/null
+++ b/src/Framework/Framework/Binding/DotvvmPropertyId.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using DotVVM.Framework.Compilation.ControlTree;
+using DotVVM.Framework.Controls;
+
+namespace DotVVM.Framework.Binding
+{
+ ///
+ /// Represents a unique ID, used as a key for .
+ ///
+ ///
+ /// The ID is a 32-bit unsigned integer, where:
+ /// - the most significant bit indicates whether the ID is of a property group (1) or a classic property (0)
+ /// - the next upper 15 bits are the (for classic properties) or (for property groups)
+ /// - the lower 16 bits are the , ID of the string key for property groups or the ID of the property for classic properties
+ /// - in case of classic properties, the LSB bit of the member ID indicates whether the property has GetValue/SetValue overrides and is not inherited (see )
+ ///
+ public readonly struct DotvvmPropertyId: IEquatable, IEquatable, IComparable
+ {
+ /// Numeric representation of the property ID.
+ public readonly uint Id;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public DotvvmPropertyId(uint id)
+ {
+ Id = id;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public DotvvmPropertyId(ushort typeOrGroupId, ushort memberId)
+ {
+ Id = ((uint)typeOrGroupId << 16) | memberId;
+ }
+
+ /// Returns true if the property is a other type of property.
+ [MemberNotNullWhen(true, nameof(PropertyGroupInstance), nameof(GroupMemberName))]
+ public bool IsPropertyGroup
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (int)Id < 0;
+ }
+ /// Returns the ID of the property declaring type
+ public ushort TypeId
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (ushort)(Id >> 16);
+ }
+ /// Returns the ID of the property group.
+ public ushort GroupId
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (ushort)((Id >> 16) ^ 0x8000);
+ }
+ /// Returns the ID of the property member, i.e. property-in-type id for classic properties, or the name ID for property groups.
+ public ushort MemberId
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (ushort)(Id & 0xFFFF);
+ }
+
+ /// Returns true if the property does not have GetValue/SetValue overrides and is not inherited. That means it is sufficient to call properties.TryGet instead going through the DotvvmProperty.GetValue dynamic dispatch
+ public bool CanUseFastAccessors
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ // properties: we encode this information as the LSB bit of the member ID (i.e. odd/even numbers)
+ // property groups: always true, i.e.
+ const uint mask = (1u << 31) | (1u);
+ const uint targetValue = 1u;
+ return (Id & mask) != targetValue;
+ }
+ }
+
+ /// Returns true if the ID is default. This ID is invalid for most purposes and can be used as a sentinel value.
+ public bool IsZero
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => Id == 0;
+ }
+
+ /// Returns the instance for this ID. Note that a new might need to be allocated.
+ public DotvvmProperty PropertyInstance => DotvvmPropertyIdAssignment.GetProperty(Id) ?? throw new Exception($"Property with ID {Id} not registered.");
+
+ /// Returns the instance for this ID, or null if the ID is of a classic property.
+
+ public DotvvmPropertyGroup? PropertyGroupInstance => !IsPropertyGroup ? null : DotvvmPropertyIdAssignment.GetPropertyGroup(GroupId);
+
+ /// Returns the name (string dictionary key) of the property group member, or null if the ID is of a classic property.
+ public string? GroupMemberName => !IsPropertyGroup ? null : DotvvmPropertyIdAssignment.GetGroupMemberName(MemberId);
+
+ /// Returns the type of the property.
+ public Type PropertyType => IsPropertyGroup ? PropertyGroupInstance.PropertyType : PropertyInstance.PropertyType;
+
+ /// Returns the property declaring type.
+ public Type DeclaringType => IsPropertyGroup ? PropertyGroupInstance.DeclaringType : DotvvmPropertyIdAssignment.GetControlType(TypeId);
+
+ /// Returns the property declaring type.
+ [MemberNotNullWhen(true, nameof(PropertyGroupInstance), nameof(GroupMemberName))]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsInPropertyGroup(ushort id) => (this.Id >> 16) == ((uint)id | 0x80_00u);
+
+ /// Constucts property ID from a property group name and a name ID.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static DotvvmPropertyId CreatePropertyGroupId(ushort groupId, ushort memberId) => new DotvvmPropertyId((ushort)(groupId | 0x80_00), memberId);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator DotvvmPropertyId(uint id) => new DotvvmPropertyId(id);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static explicit operator uint(DotvvmPropertyId id) => id.Id;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(DotvvmPropertyId other) => Id == other.Id;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(uint other) => Id == other;
+
+ public override bool Equals(object? obj) => obj is DotvvmPropertyId id && Equals(id);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode() => (int)Id;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(DotvvmPropertyId left, DotvvmPropertyId right) => left.Equals(right);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(DotvvmPropertyId left, DotvvmPropertyId right) => !left.Equals(right);
+
+ public override string ToString()
+ {
+ if (IsZero)
+ return "[0000_0000]";
+ if (IsPropertyGroup)
+ {
+ var pg = PropertyGroupInstance;
+ return $"[{TypeId:x4}_{MemberId:x4}]{pg.DeclaringType.Name}.{pg.Name}:{GroupMemberName}";
+ }
+ else
+ {
+ return $"[{TypeId:x4}_{MemberId:x4}]{PropertyInstance.FullName}";
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int CompareTo(DotvvmPropertyId other) => Id.CompareTo(other.Id);
+ }
+}
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.GroupMembers.cs b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.GroupMembers.cs
new file mode 100644
index 0000000000..1c4f4ac173
--- /dev/null
+++ b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.GroupMembers.cs
@@ -0,0 +1,401 @@
+// Generated by scripts/generate-property-ids.mjs
+using System;
+using System.Collections.Immutable;
+
+namespace DotVVM.Framework.Binding;
+
+static partial class DotvvmPropertyIdAssignment
+{
+ public static class GroupMembers
+ {
+ public const ushort accept = 1;
+ public const ushort accesskey = 2;
+ public const ushort action = 3;
+ public const ushort align = 4;
+ public const ushort allow = 5;
+ public const ushort alt = 6;
+ public const ushort aria_checked = 7;
+ public const ushort aria_controls = 8;
+ public const ushort aria_describedby = 9;
+ public const ushort aria_expanded = 10;
+ public const ushort aria_hidden = 11;
+ public const ushort aria_label = 12;
+ public const ushort aria_selected = 13;
+ public const ushort @as = 14;
+ public const ushort async = 15;
+ public const ushort autocomplete = 16;
+ public const ushort autofocus = 17;
+ public const ushort border = 18;
+ public const ushort charset = 19;
+ public const ushort @checked = 20;
+ public const ushort @class = 21;
+ public const ushort cols = 22;
+ public const ushort colspan = 23;
+ public const ushort content = 24;
+ public const ushort contenteditable = 25;
+ public const ushort crossorigin = 26;
+ public const ushort data_bind = 27;
+ public const ushort data_dismiss = 28;
+ public const ushort data_dotvvm_id = 29;
+ public const ushort data_placement = 30;
+ public const ushort data_target = 31;
+ public const ushort data_toggle = 32;
+ public const ushort data_ui = 33;
+ public const ushort data_uitest_name = 34;
+ public const ushort dir = 35;
+ public const ushort disabled = 36;
+ public const ushort download = 37;
+ public const ushort draggable = 38;
+ public const ushort enctype = 39;
+ public const ushort @for = 40;
+ public const ushort form = 41;
+ public const ushort formaction = 42;
+ public const ushort formmethod = 43;
+ public const ushort formnovalidate = 44;
+ public const ushort formtarget = 45;
+ public const ushort height = 46;
+ public const ushort hidden = 47;
+ public const ushort href = 48;
+ public const ushort hreflang = 49;
+ public const ushort http_equiv = 50;
+ public const ushort id = 51;
+ public const ushort integrity = 52;
+ public const ushort itemprop = 53;
+ public const ushort lang = 54;
+ public const ushort list = 55;
+ public const ushort loading = 56;
+ public const ushort max = 57;
+ public const ushort maxlength = 58;
+ public const ushort media = 59;
+ public const ushort method = 60;
+ public const ushort min = 61;
+ public const ushort minlength = 62;
+ public const ushort multiple = 63;
+ public const ushort name = 64;
+ public const ushort novalidate = 65;
+ public const ushort pattern = 66;
+ public const ushort ping = 67;
+ public const ushort placeholder = 68;
+ public const ushort preload = 69;
+ public const ushort @readonly = 70;
+ public const ushort referrerpolicy = 71;
+ public const ushort rel = 72;
+ public const ushort required = 73;
+ public const ushort role = 74;
+ public const ushort rows = 75;
+ public const ushort sandbox = 76;
+ public const ushort scope = 77;
+ public const ushort selected = 78;
+ public const ushort size = 79;
+ public const ushort slot = 80;
+ public const ushort span = 81;
+ public const ushort spellcheck = 82;
+ public const ushort src = 83;
+ public const ushort step = 84;
+ public const ushort style = 85;
+ public const ushort tabindex = 86;
+ public const ushort target = 87;
+ public const ushort title = 88;
+ public const ushort translate = 89;
+ public const ushort type = 90;
+ public const ushort value = 91;
+ public const ushort width = 92;
+ public const ushort wrap = 93;
+ public const ushort background_color = 94;
+ public const ushort bottom = 95;
+ public const ushort color = 96;
+ public const ushort display = 97;
+ public const ushort font_size = 98;
+ public const ushort left = 99;
+ public const ushort line_height = 100;
+ public const ushort margin_bottom = 101;
+ public const ushort margin_right = 102;
+ public const ushort margin_top = 103;
+ public const ushort margin = 104;
+ public const ushort max_height = 105;
+ public const ushort max_width = 106;
+ public const ushort min_height = 107;
+ public const ushort min_width = 108;
+ public const ushort opacity = 109;
+ public const ushort padding_bottom = 110;
+ public const ushort padding_left = 111;
+ public const ushort padding_right = 112;
+ public const ushort padding_top = 113;
+ public const ushort padding = 114;
+ public const ushort position = 115;
+ public const ushort right = 116;
+ public const ushort top = 117;
+ public const ushort visibility = 118;
+ public const ushort z_index = 119;
+ public const ushort Id = 120;
+ public const ushort Name = 121;
+ public const ushort GroupId = 122;
+ public const ushort FileName = 123;
+ public const ushort UserId = 124;
+ public const ushort Slug = 125;
+ public const ushort slug = 126;
+ public const ushort Lang = 127;
+
+ public static readonly ImmutableArray<(string Name, ushort ID)> List = ImmutableArray.Create(
+ ("accept", accept),
+ ("accesskey", accesskey),
+ ("action", action),
+ ("align", align),
+ ("allow", allow),
+ ("alt", alt),
+ ("aria-checked", aria_checked),
+ ("aria-controls", aria_controls),
+ ("aria-describedby", aria_describedby),
+ ("aria-expanded", aria_expanded),
+ ("aria-hidden", aria_hidden),
+ ("aria-label", aria_label),
+ ("aria-selected", aria_selected),
+ ("as", @as),
+ ("async", async),
+ ("autocomplete", autocomplete),
+ ("autofocus", autofocus),
+ ("border", border),
+ ("charset", charset),
+ ("checked", @checked),
+ ("class", @class),
+ ("cols", cols),
+ ("colspan", colspan),
+ ("content", content),
+ ("contenteditable", contenteditable),
+ ("crossorigin", crossorigin),
+ ("data-bind", data_bind),
+ ("data-dismiss", data_dismiss),
+ ("data-dotvvm-id", data_dotvvm_id),
+ ("data-placement", data_placement),
+ ("data-target", data_target),
+ ("data-toggle", data_toggle),
+ ("data-ui", data_ui),
+ ("data-uitest-name", data_uitest_name),
+ ("dir", dir),
+ ("disabled", disabled),
+ ("download", download),
+ ("draggable", draggable),
+ ("enctype", enctype),
+ ("for", @for),
+ ("form", form),
+ ("formaction", formaction),
+ ("formmethod", formmethod),
+ ("formnovalidate", formnovalidate),
+ ("formtarget", formtarget),
+ ("height", height),
+ ("hidden", hidden),
+ ("href", href),
+ ("hreflang", hreflang),
+ ("http-equiv", http_equiv),
+ ("id", id),
+ ("integrity", integrity),
+ ("itemprop", itemprop),
+ ("lang", lang),
+ ("list", list),
+ ("loading", loading),
+ ("max", max),
+ ("maxlength", maxlength),
+ ("media", media),
+ ("method", method),
+ ("min", min),
+ ("minlength", minlength),
+ ("multiple", multiple),
+ ("name", name),
+ ("novalidate", novalidate),
+ ("pattern", pattern),
+ ("ping", ping),
+ ("placeholder", placeholder),
+ ("preload", preload),
+ ("readonly", @readonly),
+ ("referrerpolicy", referrerpolicy),
+ ("rel", rel),
+ ("required", required),
+ ("role", role),
+ ("rows", rows),
+ ("sandbox", sandbox),
+ ("scope", scope),
+ ("selected", selected),
+ ("size", size),
+ ("slot", slot),
+ ("span", span),
+ ("spellcheck", spellcheck),
+ ("src", src),
+ ("step", step),
+ ("style", style),
+ ("tabindex", tabindex),
+ ("target", target),
+ ("title", title),
+ ("translate", translate),
+ ("type", type),
+ ("value", value),
+ ("width", width),
+ ("wrap", wrap),
+ ("background-color", background_color),
+ ("bottom", bottom),
+ ("color", color),
+ ("display", display),
+ ("font-size", font_size),
+ ("left", left),
+ ("line-height", line_height),
+ ("margin-bottom", margin_bottom),
+ ("margin-right", margin_right),
+ ("margin-top", margin_top),
+ ("margin", margin),
+ ("max-height", max_height),
+ ("max-width", max_width),
+ ("min-height", min_height),
+ ("min-width", min_width),
+ ("opacity", opacity),
+ ("padding-bottom", padding_bottom),
+ ("padding-left", padding_left),
+ ("padding-right", padding_right),
+ ("padding-top", padding_top),
+ ("padding", padding),
+ ("position", position),
+ ("right", right),
+ ("top", top),
+ ("visibility", visibility),
+ ("z-index", z_index),
+ ("Id", Id),
+ ("Name", Name),
+ ("GroupId", GroupId),
+ ("FileName", FileName),
+ ("UserId", UserId),
+ ("Slug", Slug),
+ ("slug", slug),
+ ("Lang", Lang)
+ );
+
+ public static ushort TryGetId(ReadOnlySpan attr) =>
+ attr switch {
+ "accept" => accept,
+ "accesskey" => accesskey,
+ "action" => action,
+ "align" => align,
+ "allow" => allow,
+ "alt" => alt,
+ "aria-checked" => aria_checked,
+ "aria-controls" => aria_controls,
+ "aria-describedby" => aria_describedby,
+ "aria-expanded" => aria_expanded,
+ "aria-hidden" => aria_hidden,
+ "aria-label" => aria_label,
+ "aria-selected" => aria_selected,
+ "as" => @as,
+ "async" => async,
+ "autocomplete" => autocomplete,
+ "autofocus" => autofocus,
+ "border" => border,
+ "charset" => charset,
+ "checked" => @checked,
+ "class" => @class,
+ "cols" => cols,
+ "colspan" => colspan,
+ "content" => content,
+ "contenteditable" => contenteditable,
+ "crossorigin" => crossorigin,
+ "data-bind" => data_bind,
+ "data-dismiss" => data_dismiss,
+ "data-dotvvm-id" => data_dotvvm_id,
+ "data-placement" => data_placement,
+ "data-target" => data_target,
+ "data-toggle" => data_toggle,
+ "data-ui" => data_ui,
+ "data-uitest-name" => data_uitest_name,
+ "dir" => dir,
+ "disabled" => disabled,
+ "download" => download,
+ "draggable" => draggable,
+ "enctype" => enctype,
+ "for" => @for,
+ "form" => form,
+ "formaction" => formaction,
+ "formmethod" => formmethod,
+ "formnovalidate" => formnovalidate,
+ "formtarget" => formtarget,
+ "height" => height,
+ "hidden" => hidden,
+ "href" => href,
+ "hreflang" => hreflang,
+ "http-equiv" => http_equiv,
+ "id" => id,
+ "integrity" => integrity,
+ "itemprop" => itemprop,
+ "lang" => lang,
+ "list" => list,
+ "loading" => loading,
+ "max" => max,
+ "maxlength" => maxlength,
+ "media" => media,
+ "method" => method,
+ "min" => min,
+ "minlength" => minlength,
+ "multiple" => multiple,
+ "name" => name,
+ "novalidate" => novalidate,
+ "pattern" => pattern,
+ "ping" => ping,
+ "placeholder" => placeholder,
+ "preload" => preload,
+ "readonly" => @readonly,
+ "referrerpolicy" => referrerpolicy,
+ "rel" => rel,
+ "required" => required,
+ "role" => role,
+ "rows" => rows,
+ "sandbox" => sandbox,
+ "scope" => scope,
+ "selected" => selected,
+ "size" => size,
+ "slot" => slot,
+ "span" => span,
+ "spellcheck" => spellcheck,
+ "src" => src,
+ "step" => step,
+ "style" => style,
+ "tabindex" => tabindex,
+ "target" => target,
+ "title" => title,
+ "translate" => translate,
+ "type" => type,
+ "value" => value,
+ "width" => width,
+ "wrap" => wrap,
+ "background-color" => background_color,
+ "bottom" => bottom,
+ "color" => color,
+ "display" => display,
+ "font-size" => font_size,
+ "left" => left,
+ "line-height" => line_height,
+ "margin-bottom" => margin_bottom,
+ "margin-right" => margin_right,
+ "margin-top" => margin_top,
+ "margin" => margin,
+ "max-height" => max_height,
+ "max-width" => max_width,
+ "min-height" => min_height,
+ "min-width" => min_width,
+ "opacity" => opacity,
+ "padding-bottom" => padding_bottom,
+ "padding-left" => padding_left,
+ "padding-right" => padding_right,
+ "padding-top" => padding_top,
+ "padding" => padding,
+ "position" => position,
+ "right" => right,
+ "top" => top,
+ "visibility" => visibility,
+ "z-index" => z_index,
+ "Id" => Id,
+ "Name" => Name,
+ "GroupId" => GroupId,
+ "FileName" => FileName,
+ "UserId" => UserId,
+ "Slug" => Slug,
+ "slug" => slug,
+ "Lang" => Lang,
+ _ => 0,
+ };
+ }
+}
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.PropertyGroupIds.cs b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.PropertyGroupIds.cs
new file mode 100644
index 0000000000..d2f1f580e6
--- /dev/null
+++ b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.PropertyGroupIds.cs
@@ -0,0 +1,31 @@
+// Generated by scripts/generate-property-ids.mjs
+using DotVVM.Framework.Controls;
+
+namespace DotVVM.Framework.Binding;
+
+static partial class DotvvmPropertyIdAssignment
+{
+ public static class PropertyGroupIds
+ {
+ ///
+ public const ushort HtmlGenericControl_Attributes = 1;
+
+ ///
+ public const ushort HtmlGenericControl_CssClasses = 2;
+
+ ///
+ public const ushort HtmlGenericControl_CssStyles = 3;
+
+ ///
+ public const ushort RouteLink_Params = 4;
+
+ ///
+ public const ushort RouteLink_QueryParameters = 5;
+
+ ///
+ public const ushort JsComponent_Props = 6;
+
+ ///
+ public const ushort JsComponent_Templates = 7;
+ }
+}
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.PropertyIds.cs b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.PropertyIds.cs
new file mode 100644
index 0000000000..2dde63c6f0
--- /dev/null
+++ b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.PropertyIds.cs
@@ -0,0 +1,590 @@
+// Generated by scripts/generate-property-ids.mjs
+using DotVVM.Framework.Controls;
+using DotVVM.Framework.Controls.Infrastructure;
+
+namespace DotVVM.Framework.Binding;
+
+static partial class DotvvmPropertyIdAssignment
+{
+ public static class PropertyIds
+ {
+ ///
+ public const uint DotvvmBindableObject_DataContext = TypeIds.DotvvmBindableObject << 16 | 1;
+
+ ///
+ public const uint DotvvmControl_ID = TypeIds.DotvvmControl << 16 | 2;
+
+ ///
+ public const uint DotvvmControl_ClientID = TypeIds.DotvvmControl << 16 | 4;
+
+ ///
+ public const uint DotvvmControl_IncludeInPage = TypeIds.DotvvmControl << 16 | 6;
+
+ ///
+ public const uint DotvvmControl_ClientIDMode = TypeIds.DotvvmControl << 16 | 1;
+
+ ///
+ public const uint HtmlGenericControl_Visible = TypeIds.HtmlGenericControl << 16 | 2;
+
+ ///
+ public const uint HtmlGenericControl_InnerText = TypeIds.HtmlGenericControl << 16 | 4;
+
+ ///
+ public const uint HtmlGenericControl_HtmlCapability = TypeIds.HtmlGenericControl << 16 | 1;
+
+ ///
+ public const uint Literal_Text = TypeIds.Literal << 16 | 2;
+
+ ///
+ public const uint Literal_FormatString = TypeIds.Literal << 16 | 4;
+
+ ///
+ public const uint Literal_RenderSpanElement = TypeIds.Literal << 16 | 6;
+
+ ///
+ public const uint ButtonBase_Click = TypeIds.ButtonBase << 16 | 2;
+
+ ///
+ public const uint ButtonBase_ClickArguments = TypeIds.ButtonBase << 16 | 4;
+
+ ///
+ public const uint ButtonBase_Text = TypeIds.ButtonBase << 16 | 6;
+
+ ///
+ public const uint ButtonBase_Enabled = TypeIds.ButtonBase << 16 | 1;
+
+ ///
+ public const uint ButtonBase_TextOrContentCapability = TypeIds.ButtonBase << 16 | 3;
+
+ ///
+ public const uint Button_ButtonTagName = TypeIds.Button << 16 | 2;
+
+ ///
+ public const uint Button_IsSubmitButton = TypeIds.Button << 16 | 4;
+
+ ///
+ public const uint TextBox_Text = TypeIds.TextBox << 16 | 2;
+
+ ///
+ public const uint TextBox_Changed = TypeIds.TextBox << 16 | 4;
+
+ ///
+ public const uint TextBox_Type = TypeIds.TextBox << 16 | 6;
+
+ ///
+ public const uint TextBox_TextInput = TypeIds.TextBox << 16 | 8;
+
+ ///
+ public const uint TextBox_FormatString = TypeIds.TextBox << 16 | 10;
+
+ ///
+ public const uint TextBox_SelectAllOnFocus = TypeIds.TextBox << 16 | 12;
+
+ ///
+ public const uint TextBox_Enabled = TypeIds.TextBox << 16 | 1;
+
+ ///
+ public const uint TextBox_UpdateTextOnInput = TypeIds.TextBox << 16 | 3;
+
+ ///
+ public const uint RouteLink_RouteName = TypeIds.RouteLink << 16 | 2;
+
+ ///
+ public const uint RouteLink_Enabled = TypeIds.RouteLink << 16 | 4;
+
+ ///
+ public const uint RouteLink_Text = TypeIds.RouteLink << 16 | 6;
+
+ ///
+ public const uint RouteLink_UrlSuffix = TypeIds.RouteLink << 16 | 8;
+
+ ///
+ public const uint RouteLink_Culture = TypeIds.RouteLink << 16 | 10;
+
+ ///
+ public const uint CheckableControlBase_Text = TypeIds.CheckableControlBase << 16 | 2;
+
+ ///
+ public const uint CheckableControlBase_CheckedValue = TypeIds.CheckableControlBase << 16 | 4;
+
+ ///
+ public const uint CheckableControlBase_Changed = TypeIds.CheckableControlBase << 16 | 6;
+
+ ///
+ public const uint CheckableControlBase_LabelCssClass = TypeIds.CheckableControlBase << 16 | 8;
+
+ ///
+ public const uint CheckableControlBase_InputCssClass = TypeIds.CheckableControlBase << 16 | 10;
+
+ ///
+ public const uint CheckableControlBase_ItemKeyBinding = TypeIds.CheckableControlBase << 16 | 12;
+
+ ///
+ public const uint CheckBox_Checked = TypeIds.CheckBox << 16 | 2;
+
+ ///
+ public const uint CheckBox_CheckedItems = TypeIds.CheckBox << 16 | 4;
+
+ ///
+ public const uint CheckBox_DisableIndeterminate = TypeIds.CheckBox << 16 | 6;
+
+ ///
+ public const uint RadioButton_Checked = TypeIds.RadioButton << 16 | 2;
+
+ ///
+ public const uint RadioButton_CheckedItem = TypeIds.RadioButton << 16 | 4;
+
+ ///
+ public const uint RadioButton_GroupName = TypeIds.RadioButton << 16 | 6;
+
+ ///
+ public const uint Validator_HideWhenValid = TypeIds.Validator << 16 | 1;
+
+ ///
+ public const uint Validator_InvalidCssClass = TypeIds.Validator << 16 | 3;
+
+ ///
+ public const uint Validator_SetToolTipText = TypeIds.Validator << 16 | 5;
+
+ ///
+ public const uint Validator_ShowErrorMessageText = TypeIds.Validator << 16 | 7;
+
+ ///
+ public const uint Validation_Enabled = TypeIds.Validation << 16 | 1;
+
+ ///
+ public const uint Validation_Target = TypeIds.Validation << 16 | 3;
+
+ ///
+ public const uint ValidationSummary_IncludeErrorsFromChildren = TypeIds.ValidationSummary << 16 | 2;
+
+ ///
+ public const uint ValidationSummary_HideWhenValid = TypeIds.ValidationSummary << 16 | 4;
+
+ ///
+ public const uint ValidationSummary_IncludeErrorsFromTarget = TypeIds.ValidationSummary << 16 | 6;
+
+ ///
+ public const uint ItemsControl_DataSource = TypeIds.ItemsControl << 16 | 2;
+
+ ///
+ public const uint Repeater_EmptyDataTemplate = TypeIds.Repeater << 16 | 2;
+
+ ///
+ public const uint Repeater_ItemTemplate = TypeIds.Repeater << 16 | 4;
+
+ ///
+ public const uint Repeater_RenderWrapperTag = TypeIds.Repeater << 16 | 6;
+
+ ///
+ public const uint Repeater_SeparatorTemplate = TypeIds.Repeater << 16 | 8;
+
+ ///
+ public const uint Repeater_WrapperTagName = TypeIds.Repeater << 16 | 10;
+
+ ///
+ public const uint Repeater_RenderAsNamedTemplate = TypeIds.Repeater << 16 | 12;
+
+ ///
+ public const uint HierarchyRepeater_ItemChildrenBinding = TypeIds.HierarchyRepeater << 16 | 2;
+
+ ///
+ public const uint HierarchyRepeater_ItemTemplate = TypeIds.HierarchyRepeater << 16 | 4;
+
+ ///
+ public const uint HierarchyRepeater_EmptyDataTemplate = TypeIds.HierarchyRepeater << 16 | 6;
+
+ ///
+ public const uint HierarchyRepeater_RenderWrapperTag = TypeIds.HierarchyRepeater << 16 | 8;
+
+ ///
+ public const uint HierarchyRepeater_WrapperTagName = TypeIds.HierarchyRepeater << 16 | 10;
+
+ ///
+ public const uint GridView_FilterPlacement = TypeIds.GridView << 16 | 2;
+
+ ///
+ public const uint GridView_EmptyDataTemplate = TypeIds.GridView << 16 | 4;
+
+ ///
+ public const uint GridView_Columns = TypeIds.GridView << 16 | 6;
+
+ ///
+ public const uint GridView_RowDecorators = TypeIds.GridView << 16 | 8;
+
+ ///
+ public const uint GridView_HeaderRowDecorators = TypeIds.GridView << 16 | 10;
+
+ ///
+ public const uint GridView_EditRowDecorators = TypeIds.GridView << 16 | 12;
+
+ ///
+ public const uint GridView_SortChanged = TypeIds.GridView << 16 | 14;
+
+ ///
+ public const uint GridView_ShowHeaderWhenNoData = TypeIds.GridView << 16 | 16;
+
+ ///
+ public const uint GridView_InlineEditing = TypeIds.GridView << 16 | 18;
+
+ ///
+ public const uint GridView_LoadData = TypeIds.GridView << 16 | 20;
+
+ ///
+ public const uint GridViewColumn_HeaderText = TypeIds.GridViewColumn << 16 | 2;
+
+ ///
+ public const uint GridViewColumn_HeaderTemplate = TypeIds.GridViewColumn << 16 | 4;
+
+ ///
+ public const uint GridViewColumn_FilterTemplate = TypeIds.GridViewColumn << 16 | 6;
+
+ ///
+ public const uint GridViewColumn_SortExpression = TypeIds.GridViewColumn << 16 | 8;
+
+ ///
+ public const uint GridViewColumn_SortAscendingHeaderCssClass = TypeIds.GridViewColumn << 16 | 10;
+
+ ///
+ public const uint GridViewColumn_SortDescendingHeaderCssClass = TypeIds.GridViewColumn << 16 | 12;
+
+ ///
+ public const uint GridViewColumn_AllowSorting = TypeIds.GridViewColumn << 16 | 14;
+
+ ///
+ public const uint GridViewColumn_CssClass = TypeIds.GridViewColumn << 16 | 16;
+
+ ///
+ public const uint GridViewColumn_IsEditable = TypeIds.GridViewColumn << 16 | 18;
+
+ ///
+ public const uint GridViewColumn_HeaderCssClass = TypeIds.GridViewColumn << 16 | 20;
+
+ ///
+ public const uint GridViewColumn_Width = TypeIds.GridViewColumn << 16 | 22;
+
+ ///
+ public const uint GridViewColumn_Visible = TypeIds.GridViewColumn << 16 | 24;
+
+ ///
+ public const uint GridViewColumn_CellDecorators = TypeIds.GridViewColumn << 16 | 26;
+
+ ///
+ public const uint GridViewColumn_EditCellDecorators = TypeIds.GridViewColumn << 16 | 28;
+
+ ///
+ public const uint GridViewColumn_EditTemplate = TypeIds.GridViewColumn << 16 | 30;
+
+ ///
+ public const uint GridViewColumn_HeaderCellDecorators = TypeIds.GridViewColumn << 16 | 32;
+
+ ///
+ public const uint GridViewTextColumn_FormatString = TypeIds.GridViewTextColumn << 16 | 2;
+
+ ///
+ public const uint GridViewTextColumn_ChangedBinding = TypeIds.GridViewTextColumn << 16 | 4;
+
+ ///
+ public const uint GridViewTextColumn_ValueBinding = TypeIds.GridViewTextColumn << 16 | 6;
+
+ ///
+ public const uint GridViewTextColumn_ValidatorPlacement = TypeIds.GridViewTextColumn << 16 | 8;
+
+ ///
+ public const uint GridViewCheckBoxColumn_ValueBinding = TypeIds.GridViewCheckBoxColumn << 16 | 2;
+
+ ///
+ public const uint GridViewCheckBoxColumn_ValidatorPlacement = TypeIds.GridViewCheckBoxColumn << 16 | 4;
+
+ ///
+ public const uint GridViewTemplateColumn_ContentTemplate = TypeIds.GridViewTemplateColumn << 16 | 2;
+
+ ///
+ public const uint DataPager_DataSet = TypeIds.DataPager << 16 | 2;
+
+ ///
+ public const uint DataPager_FirstPageTemplate = TypeIds.DataPager << 16 | 4;
+
+ ///
+ public const uint DataPager_LastPageTemplate = TypeIds.DataPager << 16 | 6;
+
+ ///
+ public const uint DataPager_PreviousPageTemplate = TypeIds.DataPager << 16 | 8;
+
+ ///
+ public const uint DataPager_NextPageTemplate = TypeIds.DataPager << 16 | 10;
+
+ ///
+ public const uint DataPager_RenderLinkForCurrentPage = TypeIds.DataPager << 16 | 12;
+
+ ///
+ public const uint DataPager_HideWhenOnlyOnePage = TypeIds.DataPager << 16 | 14;
+
+ ///
+ public const uint DataPager_LoadData = TypeIds.DataPager << 16 | 16;
+
+ ///
+ public const uint AppendableDataPager_LoadTemplate = TypeIds.AppendableDataPager << 16 | 2;
+
+ ///
+ public const uint AppendableDataPager_LoadingTemplate = TypeIds.AppendableDataPager << 16 | 4;
+
+ ///
+ public const uint AppendableDataPager_EndTemplate = TypeIds.AppendableDataPager << 16 | 6;
+
+ ///
+ public const uint AppendableDataPager_DataSet = TypeIds.AppendableDataPager << 16 | 8;
+
+ ///
+ public const uint AppendableDataPager_LoadData = TypeIds.AppendableDataPager << 16 | 10;
+
+ ///
+ public const uint SelectorBase_ItemTextBinding = TypeIds.SelectorBase << 16 | 2;
+
+ ///
+ public const uint SelectorBase_ItemValueBinding = TypeIds.SelectorBase << 16 | 4;
+
+ ///
+ public const uint SelectorBase_SelectionChanged = TypeIds.SelectorBase << 16 | 6;
+
+ ///
+ public const uint SelectorBase_ItemTitleBinding = TypeIds.SelectorBase << 16 | 8;
+
+ ///
+ public const uint Selector_SelectedValue = TypeIds.Selector << 16 | 2;
+
+ ///
+ public const uint MultiSelector_SelectedValues = TypeIds.MultiSelector << 16 | 2;
+
+ ///
+ public const uint ListBox_Size = TypeIds.ListBox << 16 | 2;
+
+ ///
+ public const uint ComboBox_EmptyItemText = TypeIds.ComboBox << 16 | 2;
+
+ ///
+ public const uint SelectorItem_Text = TypeIds.SelectorItem << 16 | 2;
+
+ ///
+ public const uint SelectorItem_Value = TypeIds.SelectorItem << 16 | 4;
+
+ ///
+ public const uint FileUpload_UploadedFiles = TypeIds.FileUpload << 16 | 2;
+
+ ///
+ public const uint FileUpload_Capture = TypeIds.FileUpload << 16 | 4;
+
+ ///
+ public const uint FileUpload_MaxFileSize = TypeIds.FileUpload << 16 | 6;
+
+ ///
+ public const uint FileUpload_UploadCompleted = TypeIds.FileUpload << 16 | 8;
+
+ ///
+ public const uint Timer_Command = TypeIds.Timer << 16 | 2;
+
+ ///
+ public const uint Timer_Interval = TypeIds.Timer << 16 | 4;
+
+ ///
+ public const uint Timer_Enabled = TypeIds.Timer << 16 | 6;
+
+ ///
+ public const uint UpdateProgress_Delay = TypeIds.UpdateProgress << 16 | 2;
+
+ ///
+ public const uint UpdateProgress_IncludedQueues = TypeIds.UpdateProgress << 16 | 4;
+
+ ///
+ public const uint UpdateProgress_ExcludedQueues = TypeIds.UpdateProgress << 16 | 6;
+
+ ///
+ public const uint Label_For = TypeIds.Label << 16 | 2;
+
+ ///
+ public const uint EmptyData_WrapperTagName = TypeIds.EmptyData << 16 | 2;
+
+ ///
+ public const uint EmptyData_RenderWrapperTag = TypeIds.EmptyData << 16 | 4;
+
+ ///
+ public const uint Content_ContentPlaceHolderID = TypeIds.Content << 16 | 2;
+
+ ///
+ public const uint TemplateHost_Template = TypeIds.TemplateHost << 16 | 2;
+
+ ///
+ public const uint AddTemplateDecorator_AfterTemplate = TypeIds.AddTemplateDecorator << 16 | 2;
+
+ ///
+ public const uint AddTemplateDecorator_BeforeTemplate = TypeIds.AddTemplateDecorator << 16 | 4;
+
+ ///
+ public const uint SpaContentPlaceHolder_DefaultRouteName = TypeIds.SpaContentPlaceHolder << 16 | 2;
+
+ ///
+ public const uint SpaContentPlaceHolder_PrefixRouteName = TypeIds.SpaContentPlaceHolder << 16 | 4;
+
+ ///
+ public const uint SpaContentPlaceHolder_UseHistoryApi = TypeIds.SpaContentPlaceHolder << 16 | 6;
+
+ ///
+ public const uint ModalDialog_Open = TypeIds.ModalDialog << 16 | 2;
+
+ ///
+ public const uint ModalDialog_CloseOnBackdropClick = TypeIds.ModalDialog << 16 | 4;
+
+ ///
+ public const uint ModalDialog_Close = TypeIds.ModalDialog << 16 | 6;
+
+ ///
+ public const uint HtmlLiteral_Html = TypeIds.HtmlLiteral << 16 | 2;
+
+ ///
+ public const uint RequiredResource_Name = TypeIds.RequiredResource << 16 | 2;
+
+ ///
+ public const uint InlineScript_Dependencies = TypeIds.InlineScript << 16 | 2;
+
+ ///
+ public const uint InlineScript_Script = TypeIds.InlineScript << 16 | 4;
+
+ ///
+ public const uint RoleView_Roles = TypeIds.RoleView << 16 | 2;
+
+ ///
+ public const uint RoleView_IsMemberTemplate = TypeIds.RoleView << 16 | 4;
+
+ ///
+ public const uint RoleView_IsNotMemberTemplate = TypeIds.RoleView << 16 | 6;
+
+ ///
+ public const uint RoleView_HideForAnonymousUsers = TypeIds.RoleView << 16 | 8;
+
+ ///
+ public const uint ClaimView_Claim = TypeIds.ClaimView << 16 | 2;
+
+ ///
+ public const uint ClaimView_Values = TypeIds.ClaimView << 16 | 4;
+
+ ///
+ public const uint ClaimView_HasClaimTemplate = TypeIds.ClaimView << 16 | 6;
+
+ ///
+ public const uint ClaimView_HideForAnonymousUsers = TypeIds.ClaimView << 16 | 8;
+
+ ///
+ public const uint EnvironmentView_Environments = TypeIds.EnvironmentView << 16 | 2;
+
+ ///
+ public const uint EnvironmentView_IsEnvironmentTemplate = TypeIds.EnvironmentView << 16 | 4;
+
+ ///
+ public const uint EnvironmentView_IsNotEnvironmentTemplate = TypeIds.EnvironmentView << 16 | 6;
+
+ ///
+ public const uint JsComponent_Global = TypeIds.JsComponent << 16 | 2;
+
+ ///
+ public const uint JsComponent_Name = TypeIds.JsComponent << 16 | 4;
+
+ ///
+ public const uint JsComponent_WrapperTagName = TypeIds.JsComponent << 16 | 6;
+
+ ///
+ public const uint PostBackHandler_EventName = TypeIds.PostBackHandler << 16 | 2;
+
+ ///
+ public const uint PostBackHandler_Enabled = TypeIds.PostBackHandler << 16 | 4;
+
+ ///
+ public const uint SuppressPostBackHandler_Suppress = TypeIds.SuppressPostBackHandler << 16 | 2;
+
+ ///
+ public const uint ConcurrencyQueueSetting_EventName = TypeIds.ConcurrencyQueueSetting << 16 | 2;
+
+ ///
+ public const uint ConcurrencyQueueSetting_ConcurrencyQueue = TypeIds.ConcurrencyQueueSetting << 16 | 4;
+
+ ///
+ public const uint NamedCommand_Name = TypeIds.NamedCommand << 16 | 2;
+
+ ///
+ public const uint NamedCommand_Command = TypeIds.NamedCommand << 16 | 4;
+
+ ///
+ public const uint PostBack_Update = TypeIds.PostBack << 16 | 2;
+
+ ///
+ public const uint PostBack_Handlers = TypeIds.PostBack << 16 | 4;
+
+ ///
+ public const uint PostBack_ConcurrencyQueueSettings = TypeIds.PostBack << 16 | 6;
+
+ ///
+ public const uint PostBack_Concurrency = TypeIds.PostBack << 16 | 1;
+
+ ///
+ public const uint PostBack_ConcurrencyQueue = TypeIds.PostBack << 16 | 3;
+
+ ///
+ public const uint FormControls_Enabled = TypeIds.FormControls << 16 | 1;
+
+ ///
+ public const uint UITests_GenerateStub = TypeIds.UITests << 16 | 1;
+
+ ///
+ public const uint Internal_UniqueID = TypeIds.Internal << 16 | 2;
+
+ ///
+ public const uint Internal_IsNamingContainer = TypeIds.Internal << 16 | 4;
+
+ ///
+ public const uint Internal_IsControlBindingTarget = TypeIds.Internal << 16 | 6;
+
+ ///
+ public const uint Internal_PathFragment = TypeIds.Internal << 16 | 8;
+
+ ///
+ public const uint Internal_IsServerOnlyDataContext = TypeIds.Internal << 16 | 10;
+
+ ///
+ public const uint Internal_MarkupLineNumber = TypeIds.Internal << 16 | 12;
+
+ ///
+ public const uint Internal_ClientIDFragment = TypeIds.Internal << 16 | 14;
+
+ ///
+ public const uint Internal_IsMasterPageCompositionFinished = TypeIds.Internal << 16 | 16;
+
+ ///
+ public const uint Internal_CurrentIndexBinding = TypeIds.Internal << 16 | 18;
+
+ ///
+ public const uint Internal_ReferencedViewModuleInfo = TypeIds.Internal << 16 | 20;
+
+ ///
+ public const uint Internal_UsedPropertiesInfo = TypeIds.Internal << 16 | 22;
+
+ ///
+ public const uint Internal_IsSpaPage = TypeIds.Internal << 16 | 1;
+
+ ///
+ public const uint Internal_UseHistoryApiSpaNavigation = TypeIds.Internal << 16 | 3;
+
+ ///
+ public const uint Internal_DataContextType = TypeIds.Internal << 16 | 5;
+
+ ///
+ public const uint Internal_MarkupFileName = TypeIds.Internal << 16 | 7;
+
+ ///
+ public const uint Internal_RequestContext = TypeIds.Internal << 16 | 9;
+
+ ///
+ public const uint RenderSettings_Mode = TypeIds.RenderSettings << 16 | 1;
+
+ ///
+ public const uint DotvvmView_Directives = TypeIds.DotvvmView << 16 | 1;
+ }
+}
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.TypeIds.cs b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.TypeIds.cs
new file mode 100644
index 0000000000..248064e93d
--- /dev/null
+++ b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.TypeIds.cs
@@ -0,0 +1,139 @@
+// Generated by scripts/generate-property-ids.mjs
+using System;
+using System.Collections.Immutable;
+using DotVVM.Framework.Controls;
+using DotVVM.Framework.Controls.Infrastructure;
+
+namespace DotVVM.Framework.Binding;
+
+static partial class DotvvmPropertyIdAssignment
+{
+ public static class TypeIds
+ {
+ public const ushort DotvvmBindableObject = 1;
+ public const ushort DotvvmControl = 2;
+ public const ushort HtmlGenericControl = 3;
+ public const ushort RawLiteral = 4;
+ public const ushort Literal = 5;
+ public const ushort ButtonBase = 6;
+ public const ushort Button = 7;
+ public const ushort LinkButton = 8;
+ public const ushort TextBox = 9;
+ public const ushort RouteLink = 10;
+ public const ushort CheckableControlBase = 11;
+ public const ushort CheckBox = 12;
+ public const ushort RadioButton = 13;
+ public const ushort Validator = 14;
+ public const ushort Validation = 15;
+ public const ushort ValidationSummary = 16;
+ public const ushort ItemsControl = 17;
+ public const ushort Repeater = 18;
+ public const ushort HierarchyRepeater = 19;
+ public const ushort GridView = 20;
+ public const ushort GridViewColumn = 21;
+ public const ushort GridViewTextColumn = 22;
+ public const ushort GridViewCheckBoxColumn = 23;
+ public const ushort GridViewTemplateColumn = 24;
+ public const ushort DataPager = 25;
+ public const ushort AppendableDataPager = 26;
+ public const ushort SelectorBase = 27;
+ public const ushort Selector = 28;
+ public const ushort MultiSelector = 29;
+ public const ushort ListBox = 30;
+ public const ushort ComboBox = 31;
+ public const ushort SelectorItem = 32;
+ public const ushort FileUpload = 33;
+ public const ushort Timer = 34;
+ public const ushort UpdateProgress = 35;
+ public const ushort Label = 36;
+ public const ushort EmptyData = 37;
+ public const ushort Content = 38;
+ public const ushort TemplateHost = 39;
+ public const ushort AddTemplateDecorator = 40;
+ public const ushort SpaContentPlaceHolder = 41;
+ public const ushort ModalDialog = 42;
+ public const ushort HtmlLiteral = 43;
+ public const ushort RequiredResource = 44;
+ public const ushort InlineScript = 45;
+ public const ushort RoleView = 46;
+ public const ushort ClaimView = 47;
+ public const ushort EnvironmentView = 48;
+ public const ushort JsComponent = 49;
+ public const ushort PostBackHandler = 50;
+ public const ushort SuppressPostBackHandler = 51;
+ public const ushort ConcurrencyQueueSetting = 52;
+ public const ushort NamedCommand = 53;
+ public const ushort PostBack = 54;
+ public const ushort FormControls = 55;
+ public const ushort UITests = 56;
+ public const ushort Events = 57;
+ public const ushort Styles = 58;
+ public const ushort Internal = 59;
+ public const ushort RenderSettings = 60;
+ public const ushort DotvvmView = 61;
+
+ public static readonly ImmutableArray<(Type type, ushort id)> List = ImmutableArray.Create(
+ (typeof(DotvvmBindableObject), DotvvmBindableObject),
+ (typeof(DotvvmControl), DotvvmControl),
+ (typeof(HtmlGenericControl), HtmlGenericControl),
+ (typeof(RawLiteral), RawLiteral),
+ (typeof(Literal), Literal),
+ (typeof(ButtonBase), ButtonBase),
+ (typeof(Button), Button),
+ (typeof(LinkButton), LinkButton),
+ (typeof(TextBox), TextBox),
+ (typeof(RouteLink), RouteLink),
+ (typeof(CheckableControlBase), CheckableControlBase),
+ (typeof(CheckBox), CheckBox),
+ (typeof(RadioButton), RadioButton),
+ (typeof(Validator), Validator),
+ (typeof(Validation), Validation),
+ (typeof(ValidationSummary), ValidationSummary),
+ (typeof(ItemsControl), ItemsControl),
+ (typeof(Repeater), Repeater),
+ (typeof(HierarchyRepeater), HierarchyRepeater),
+ (typeof(GridView), GridView),
+ (typeof(GridViewColumn), GridViewColumn),
+ (typeof(GridViewTextColumn), GridViewTextColumn),
+ (typeof(GridViewCheckBoxColumn), GridViewCheckBoxColumn),
+ (typeof(GridViewTemplateColumn), GridViewTemplateColumn),
+ (typeof(DataPager), DataPager),
+ (typeof(AppendableDataPager), AppendableDataPager),
+ (typeof(SelectorBase), SelectorBase),
+ (typeof(Selector), Selector),
+ (typeof(MultiSelector), MultiSelector),
+ (typeof(ListBox), ListBox),
+ (typeof(ComboBox), ComboBox),
+ (typeof(SelectorItem), SelectorItem),
+ (typeof(FileUpload), FileUpload),
+ (typeof(Timer), Timer),
+ (typeof(UpdateProgress), UpdateProgress),
+ (typeof(Label), Label),
+ (typeof(EmptyData), EmptyData),
+ (typeof(Content), Content),
+ (typeof(TemplateHost), TemplateHost),
+ (typeof(AddTemplateDecorator), AddTemplateDecorator),
+ (typeof(SpaContentPlaceHolder), SpaContentPlaceHolder),
+ (typeof(ModalDialog), ModalDialog),
+ (typeof(HtmlLiteral), HtmlLiteral),
+ (typeof(RequiredResource), RequiredResource),
+ (typeof(InlineScript), InlineScript),
+ (typeof(RoleView), RoleView),
+ (typeof(ClaimView), ClaimView),
+ (typeof(EnvironmentView), EnvironmentView),
+ (typeof(JsComponent), JsComponent),
+ (typeof(PostBackHandler), PostBackHandler),
+ (typeof(SuppressPostBackHandler), SuppressPostBackHandler),
+ (typeof(ConcurrencyQueueSetting), ConcurrencyQueueSetting),
+ (typeof(NamedCommand), NamedCommand),
+ (typeof(PostBack), PostBack),
+ (typeof(FormControls), FormControls),
+ (typeof(UITests), UITests),
+ (typeof(Events), Events),
+ (typeof(Styles), Styles),
+ (typeof(Internal), Internal),
+ (typeof(RenderSettings), RenderSettings),
+ (typeof(DotvvmView), DotvvmView)
+ );
+ }
+}
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.cs b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.cs
new file mode 100644
index 0000000000..b4bd6033cc
--- /dev/null
+++ b/src/Framework/Framework/Binding/DotvvmPropertyIdAssignment.cs
@@ -0,0 +1,484 @@
+using System;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Numerics;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using DotVVM.Framework.Compilation.ControlTree;
+using DotVVM.Framework.Controls;
+using FastExpressionCompiler;
+
+namespace DotVVM.Framework.Binding
+{
+
+ internal static partial class DotvvmPropertyIdAssignment
+ {
+ /// Type and property group IDs bellow this are reserved for manual ID assignment
+ const int RESERVED_CONTROL_TYPES = 256;
+ /// Properties with ID bellow this are reserved for manual ID assignment (only makes sense for controls with manual type ID)
+ const int RESERVED_PROPERTY_COUNT = 32;
+ const int DEFAULT_PROPERTY_COUNT = 16;
+ static readonly ConcurrentDictionary typeIds = new(concurrencyLevel: 1, capacity: 256);
+ private static readonly object controlTypeRegisterLock = new object();
+ private static int controlCounter = RESERVED_CONTROL_TYPES; // first 256 types are reserved for DotVVM controls
+ private static ControlTypeInfo[] controls = new ControlTypeInfo[1024];
+ private static readonly object groupRegisterLock = new object();
+ private static int groupCounter = RESERVED_CONTROL_TYPES; // first 256 types are reserved for DotVVM controls
+ private static DotvvmPropertyGroup?[] propertyGroups = new DotvvmPropertyGroup[1024];
+ private static ulong[] propertyGroupActiveBitmap = new ulong[1024 / 64];
+ static readonly ConcurrentDictionary propertyGroupMemberIds = new(concurrencyLevel: 1, capacity: 256);
+ private static readonly object groupMemberRegisterLock = new object();
+ static string?[] propertyGroupMemberNames = new string[1024];
+
+ static DotvvmPropertyIdAssignment()
+ {
+ foreach (var (type, id) in TypeIds.List)
+ {
+ typeIds[type] = id;
+ }
+ foreach (var (name, id) in GroupMembers.List)
+ {
+ propertyGroupMemberIds[name] = id;
+ propertyGroupMemberNames[id] = name;
+ }
+ }
+
+#region Optimized metadata accessors
+ /// Equivalent to
+ public static bool IsInherited(DotvvmPropertyId propertyId)
+ {
+ if (propertyId.CanUseFastAccessors)
+ return false;
+
+ return BitmapRead(controls[propertyId.TypeId].inheritedBitmap, propertyId.MemberId);
+ }
+
+ /// Returns if the DotvvmProperty uses standard GetValue/SetValue method and we can avoid the dynamic dispatch
+ ///
+ public static bool UsesStandardAccessors(DotvvmPropertyId propertyId)
+ {
+ if (propertyId.CanUseFastAccessors)
+ {
+ return true;
+ }
+ else
+ {
+ var bitmap = controls[propertyId.TypeId].standardBitmap;
+ var index = propertyId.MemberId;
+ return BitmapRead(bitmap, index);
+ }
+ }
+
+ /// Returns if the given property is of the or type
+ public static bool IsActive(DotvvmPropertyId propertyId)
+ {
+ Debug.Assert(GetProperty(propertyId) != null, $"Property {propertyId} not registered.");
+ ulong[] bitmap;
+ uint index;
+ if (propertyId.IsPropertyGroup)
+ {
+ bitmap = propertyGroupActiveBitmap;
+ index = propertyId.GroupId;
+ }
+ else
+ {
+ bitmap = controls[propertyId.TypeId].activeBitmap;
+ index = propertyId.MemberId;
+ }
+ return BitmapRead(bitmap, index);
+ }
+
+ /// Returns the DotvvmProperty with a given ID, or returns null if no such property exists. New instance of might be created.
+ public static DotvvmProperty? GetProperty(DotvvmPropertyId id)
+ {
+ if (id.IsPropertyGroup)
+ {
+ var groupIx = id.GroupId;
+ if (groupIx >= propertyGroups.Length)
+ return null;
+ var group = propertyGroups[groupIx];
+ if (group is null)
+ return null;
+
+ return group.GetDotvvmProperty(id.MemberId);
+ }
+ else
+ {
+ var typeId = id.TypeId;
+ if (typeId >= controls.Length)
+ return null;
+ var typeProps = controls[typeId].properties;
+ if (typeProps is null)
+ return null;
+ return typeProps[id.MemberId];
+ }
+ }
+
+ /// Returns the or with the given id
+ public static Compilation.IControlAttributeDescriptor? GetPropertyOrPropertyGroup(DotvvmPropertyId id)
+ {
+ if (id.IsPropertyGroup)
+ {
+ var groupIx = id.GroupId;
+ if (groupIx >= propertyGroups.Length)
+ return null;
+ return propertyGroups[groupIx];
+ }
+ else
+ {
+ var typeId = id.TypeId;
+ if (typeId >= controls.Length)
+ return null;
+ var typeProps = controls[typeId].properties;
+ if (typeProps is null)
+ return null;
+ return typeProps[id.MemberId];
+ }
+ }
+
+ /// Returns the value of the property or property group. If the property is not set, returns the default value.
+ public static object? GetValueRaw(DotvvmBindableObject obj, DotvvmPropertyId id, bool inherit = true)
+ {
+ if (id.CanUseFastAccessors)
+ {
+ if (obj.properties.TryGet(id, out var value))
+ return value;
+
+ if (id.IsPropertyGroup)
+ return propertyGroups[id.GroupId]!.DefaultValue;
+ else
+ return controls[id.TypeId].properties[id.MemberId]!.DefaultValue;
+ }
+ else
+ {
+ var property = controls[id.TypeId].properties[id.MemberId];
+ return property!.GetValue(obj, inherit);
+ }
+ }
+
+ /// Returns the value of the property or property group. If the property is not set, returns the default value.
+ public static MarkupOptionsAttribute GetMarkupOptions(DotvvmPropertyId id)
+ {
+ if (id.IsPropertyGroup)
+ {
+ var groupIx = id.GroupId;
+ return propertyGroups[groupIx]!.MarkupOptions;
+ }
+ else
+ {
+ var typeId = id.TypeId;
+ var typeProps = controls[typeId].properties;
+ return typeProps[id.MemberId]!.MarkupOptions;
+ }
+ }
+
+ /// Property or property group has type assignable to IBinding and bindings should not be evaluated in GetValue
+ ///
+ public static bool IsBindingProperty(DotvvmPropertyId id)
+ {
+ if (id.IsPropertyGroup)
+ {
+ var groupIx = id.GroupId;
+ return propertyGroups[groupIx]!.IsBindingProperty;
+ }
+ else
+ {
+ var typeId = id.TypeId;
+ var typeProps = controls[typeId].properties;
+ return typeProps[id.MemberId]!.IsBindingProperty;
+ }
+ }
+
+ public static DotvvmPropertyGroup? GetPropertyGroup(ushort id)
+ {
+ if (id >= propertyGroups.Length)
+ return null;
+ return propertyGroups[id];
+ }
+#endregion
+
+#region Registration
+ public static Type GetControlType(ushort id)
+ {
+ if (id == 0 || id >= controls.Length)
+ throw new ArgumentOutOfRangeException(nameof(id), id, "Control type ID is invalid.");
+ return controls[id].controlType;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort RegisterType(Type type)
+ {
+ if (typeIds.TryGetValue(type, out var existingId) && controls[existingId].locker is {})
+ return existingId;
+
+ return unlikely(type);
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static ushort unlikely(Type type)
+ {
+ Span ids = stackalloc ushort[1];
+ RegisterTypes([type], ids);
+ return ids[0];
+ }
+ }
+ public static void RegisterTypes(ReadOnlySpan types, Span ids)
+ {
+ if (types.Length == 0)
+ return;
+
+ lock (controlTypeRegisterLock)
+ {
+ if (controlCounter + types.Length >= controls.Length)
+ {
+#if NET6_0_OR_GREATER
+ var nextPow2 = 1 << (BitOperations.Log2((uint)(controlCounter + types.Length)) + 1);
+#else
+ var nextPow2 = types.Length * 2;
+ while (nextPow2 < controlCounter + types.Length)
+ nextPow2 *= 2;
+#endif
+ VolatileResize(ref controls, nextPow2);
+ }
+ for (int i = 0; i < types.Length; i++)
+ {
+ var type = types[i];
+ if (!typeIds.TryGetValue(type, out var id))
+ {
+ id = (ushort)controlCounter++;
+ }
+ if (controls[id].locker is null)
+ {
+ controls[id].locker = new object();
+ controls[id].controlType = type;
+ controls[id].properties = new DotvvmProperty[DEFAULT_PROPERTY_COUNT];
+ controls[id].inheritedBitmap = new ulong[(DEFAULT_PROPERTY_COUNT - 1) / 64 + 1];
+ controls[id].standardBitmap = new ulong[(DEFAULT_PROPERTY_COUNT - 1) / 64 + 1];
+ controls[id].activeBitmap = new ulong[(DEFAULT_PROPERTY_COUNT - 1) / 64 + 1];
+ if (id < RESERVED_CONTROL_TYPES)
+ {
+ controls[id].counterStandard = RESERVED_PROPERTY_COUNT;
+ controls[id].counterNonStandard = RESERVED_PROPERTY_COUNT;
+ }
+ typeIds[type] = id;
+ }
+ ids[i] = id;
+ }
+ }
+ }
+
+ public static DotvvmPropertyId RegisterProperty(DotvvmProperty property)
+ {
+ if (property.GetType() == typeof(GroupedDotvvmProperty))
+ throw new ArgumentException("RegisterProperty cannot be called with GroupedDotvvmProperty!");
+
+ var typeCanUseDirectAccess = TypeCanUseAnyDirectAccess(property.GetType());
+ var canUseDirectAccess = !property.IsValueInherited && typeCanUseDirectAccess;
+
+ var typeId = RegisterType(property.DeclaringType);
+ ref ControlTypeInfo control = ref controls[typeId];
+ lock (control.locker) // single control registrations are sequential anyway (most likely)
+ {
+ uint id;
+ if (typeId < RESERVED_CONTROL_TYPES &&
+ typeof(PropertyIds).GetField(property.DeclaringType.Name + "_" + property.Name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null) is {} predefinedId)
+ {
+ id = (uint)predefinedId;
+ if ((id & 0xffff) == 0)
+ throw new InvalidOperationException($"Predefined property ID of {property} cannot be 0.");
+ if (id >> 16 != typeId)
+ throw new InvalidOperationException($"Predefined property ID of {property} does not match the property declaring type ID.");
+ if ((id & 0xffff) > RESERVED_PROPERTY_COUNT)
+ throw new InvalidOperationException($"Predefined property ID of {property} is too high (there is only {RESERVED_PROPERTY_COUNT} reserved slots).");
+ if (canUseDirectAccess != (id % 2 == 0))
+ throw new InvalidOperationException($"Predefined property ID of {property} does not match the property canUseDirectAccess={canUseDirectAccess}. The ID must be {(canUseDirectAccess ? "even" : "odd")} number.");
+ id = id & 0xffff;
+ }
+ else if (canUseDirectAccess)
+ {
+ control.counterStandard += 1;
+ id = control.counterStandard * 2;
+ }
+ else
+ {
+ control.counterNonStandard += 1;
+ id = control.counterNonStandard * 2 + 1;
+ }
+ if (id > ushort.MaxValue)
+ ThrowTooManyException(property);
+
+ // resize arrays (we hold a write lock, but others may be reading in parallel)
+ while (id >= control.properties.Length)
+ {
+ VolatileResize(ref control.properties, control.properties.Length * 2);
+ }
+ while (id / 64 >= control.inheritedBitmap.Length)
+ {
+ Debug.Assert(control.inheritedBitmap.Length == control.standardBitmap.Length);
+ Debug.Assert(control.inheritedBitmap.Length == control.activeBitmap.Length);
+
+ VolatileResize(ref control.inheritedBitmap, control.inheritedBitmap.Length * 2);
+ VolatileResize(ref control.standardBitmap, control.standardBitmap.Length * 2);
+ VolatileResize(ref control.activeBitmap, control.activeBitmap.Length * 2);
+ }
+
+ if (property.IsValueInherited)
+ BitmapSet(control.inheritedBitmap, (uint)id);
+ if (typeCanUseDirectAccess)
+ BitmapSet(control.standardBitmap, (uint)id);
+ if (property is ActiveDotvvmProperty)
+ BitmapSet(control.activeBitmap, (uint)id);
+
+ control.properties[id] = property;
+ return new DotvvmPropertyId(typeId, (ushort)id);
+ }
+
+ static void ThrowTooManyException(DotvvmProperty property) =>
+ throw new Exception($"Too many properties registered for a single control type ({property.DeclaringType.ToCode()}). Trying to register property '{property.Name}: {property.PropertyType.ToCode()}'");
+ }
+
+ private static readonly ConcurrentDictionary cacheTypeCanUseDirectAccess = new(concurrencyLevel: 1, capacity: 10);
+
+ /// Does the property use the default GetValue/SetValue methods?
+ public static (bool getter, bool setter) TypeCanUseDirectAccess(Type propertyType)
+ {
+ if (propertyType == typeof(DotvvmProperty) || propertyType == typeof(GroupedDotvvmProperty))
+ return (true, true);
+
+ if (propertyType == typeof(DotvvmCapabilityProperty) || propertyType == typeof(DotvvmPropertyAlias) || propertyType == typeof(CompileTimeOnlyDotvvmProperty))
+ return (false, false);
+
+ if (propertyType.IsGenericType)
+ {
+ propertyType = propertyType.GetGenericTypeDefinition();
+ if (propertyType == typeof(DelegateActionProperty<>))
+ return (true, true);
+ }
+
+ return cacheTypeCanUseDirectAccess.GetOrAdd(propertyType, static t =>
+ {
+ var getter = t.GetMethod(nameof(DotvvmProperty.GetValue), new [] { typeof(DotvvmBindableObject), typeof(bool) })!.DeclaringType == typeof(DotvvmProperty);
+ var setter = t.GetMethod(nameof(DotvvmProperty.SetValue), new [] { typeof(DotvvmBindableObject), typeof(object) })!.DeclaringType == typeof(DotvvmProperty);
+ return (getter, setter);
+ });
+ }
+ public static bool TypeCanUseAnyDirectAccess(Type propertyType)
+ {
+ var (getter, setter) = TypeCanUseDirectAccess(propertyType);
+ return getter && setter;
+ }
+
+ public static ushort RegisterPropertyGroup(DotvvmPropertyGroup group)
+ {
+ lock (groupRegisterLock)
+ {
+ ushort id;
+
+ // Check for predefined property group ID using reflection (similar to property registration)
+ var declaringTypeId = RegisterType(group.DeclaringType);
+ if (declaringTypeId < RESERVED_CONTROL_TYPES &&
+ typeof(PropertyGroupIds).GetField(group.DeclaringType.Name + "_" + group.Name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null) is {} predefinedId)
+ {
+ id = (ushort)predefinedId;
+ if (id == 0)
+ throw new InvalidOperationException($"Predefined property group ID of {group} cannot be 0.");
+ if (id > RESERVED_CONTROL_TYPES)
+ throw new InvalidOperationException($"Predefined property group ID of {group} is too high (there is only {RESERVED_CONTROL_TYPES} reserved slots).");
+ }
+ else
+ {
+ id = (ushort)groupCounter++;
+ if (id == 0)
+ throw new Exception("Too many property groups registered already.");
+ }
+
+ if (id >= propertyGroups.Length)
+ {
+ VolatileResize(ref propertyGroups, propertyGroups.Length * 2);
+ VolatileResize(ref propertyGroupActiveBitmap, propertyGroupActiveBitmap.Length * 2);
+ }
+
+ propertyGroups[id] = group;
+ if (group is ActiveDotvvmPropertyGroup)
+ BitmapSet(propertyGroupActiveBitmap, id);
+ return id;
+ }
+ }
+
+ /// Thread-safe to read from the array while we are resizing
+ private static void VolatileResize(ref T[] array, int newSize)
+ {
+ var localRef = array;
+ var newArray = new T[newSize];
+ localRef.AsSpan().CopyTo(newArray.AsSpan(0, localRef.Length));
+ Volatile.Write(ref array, newArray);
+ }
+
+#endregion Registration
+
+#region Group members
+ public static ushort GetGroupMemberId(string name, bool registerIfNotFound)
+ {
+ var id = GroupMembers.TryGetId(name.AsSpan());
+ if (id != 0)
+ return id;
+ if (propertyGroupMemberIds.TryGetValue(name, out id))
+ return id;
+ if (!registerIfNotFound)
+ return 0;
+ return RegisterGroupMember(name);
+ }
+
+ private static ushort RegisterGroupMember(string name)
+ {
+ lock (groupMemberRegisterLock)
+ {
+ if (propertyGroupMemberIds.TryGetValue(name, out var id))
+ return id;
+ id = (ushort)(propertyGroupMemberIds.Count + 1);
+ if (id == 0)
+ throw new Exception("Too many property group members registered already.");
+ if (id >= propertyGroupMemberNames.Length)
+ VolatileResize(ref propertyGroupMemberNames, propertyGroupMemberNames.Length * 2);
+ propertyGroupMemberNames[id] = name;
+ propertyGroupMemberIds[name] = id;
+ return id;
+ }
+ }
+
+ internal static string? GetGroupMemberName(ushort id)
+ {
+ if (id < propertyGroupMemberNames.Length)
+ return propertyGroupMemberNames[id];
+ return null;
+ }
+#endregion Group members
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ static bool BitmapRead(ulong[] bitmap, uint index)
+ {
+ return (bitmap[index / 64] & (1ul << (int)(index % 64))) != 0;
+ }
+
+ static void BitmapSet(ulong[] bitmap, uint index)
+ {
+ bitmap[index / 64] |= 1ul << (int)(index % 64);
+ }
+
+ private struct ControlTypeInfo
+ {
+ public DotvvmProperty?[] properties;
+ /// Bitmap for
+ public ulong[] inheritedBitmap;
+ /// Bitmap for
+ public ulong[] standardBitmap;
+ /// Bitmap storing if property is
+ public ulong[] activeBitmap;
+ /// TODO split struct to part used during registration and part at runtime for lookups
+ public object locker;
+ public Type controlType;
+ public uint counterStandard;
+ public uint counterNonStandard;
+ }
+ }
+}
diff --git a/src/Framework/Framework/Binding/DotvvmPropertyWithFallback.cs b/src/Framework/Framework/Binding/DotvvmPropertyWithFallback.cs
index 08ede07bb6..79263b228b 100644
--- a/src/Framework/Framework/Binding/DotvvmPropertyWithFallback.cs
+++ b/src/Framework/Framework/Binding/DotvvmPropertyWithFallback.cs
@@ -16,7 +16,7 @@ public sealed class DotvvmPropertyWithFallback : DotvvmProperty
///
public DotvvmProperty FallbackProperty { get; private set; }
- public DotvvmPropertyWithFallback(DotvvmProperty fallbackProperty)
+ public DotvvmPropertyWithFallback(DotvvmProperty fallbackProperty, string name, Type declaringType, bool isValueInherited): base(name, declaringType, isValueInherited)
{
this.FallbackProperty = fallbackProperty;
}
@@ -61,7 +61,7 @@ public static DotvvmProperty Register(Expression<
/// Indicates whether the value can be inherited from the parent controls.
public static DotvvmPropertyWithFallback Register(string propertyName, DotvvmProperty fallbackProperty, bool isValueInherited = false)
{
- var property = new DotvvmPropertyWithFallback(fallbackProperty);
+ var property = new DotvvmPropertyWithFallback(fallbackProperty, propertyName, typeof(TDeclaringType), isValueInherited: isValueInherited);
Register(propertyName, isValueInherited: isValueInherited, property: property);
property.DefaultValue = fallbackProperty.DefaultValue;
return property;
@@ -69,7 +69,7 @@ public static DotvvmPropertyWithFallback Register
private bool TryGetValue(DotvvmBindableObject control, out object? value, bool inherit = true)
{
- if (control.properties.TryGet(this, out value))
+ if (control.properties.TryGet(Id, out value))
{
return true;
}
diff --git a/src/Framework/Framework/Binding/GroupedDotvvmProperty.cs b/src/Framework/Framework/Binding/GroupedDotvvmProperty.cs
index 4f5f91c0d1..8556b39e87 100644
--- a/src/Framework/Framework/Binding/GroupedDotvvmProperty.cs
+++ b/src/Framework/Framework/Binding/GroupedDotvvmProperty.cs
@@ -16,28 +16,26 @@ public sealed class GroupedDotvvmProperty : DotvvmProperty, IGroupedPropertyDesc
IPropertyGroupDescriptor IGroupedPropertyDescriptor.PropertyGroup => PropertyGroup;
- public GroupedDotvvmProperty(string groupMemberName, DotvvmPropertyGroup propertyGroup)
+ private GroupedDotvvmProperty(string memberName, ushort memberId, DotvvmPropertyGroup group)
+ : base(DotvvmPropertyId.CreatePropertyGroupId(group.Id, memberId), group.Name + ":" + memberName, group.DeclaringType)
{
- this.GroupMemberName = groupMemberName;
- this.PropertyGroup = propertyGroup;
+ this.GroupMemberName = memberName;
+ this.PropertyGroup = group;
}
- public static GroupedDotvvmProperty Create(DotvvmPropertyGroup group, string name)
+ public static GroupedDotvvmProperty Create(DotvvmPropertyGroup group, string name, ushort id)
{
- var propname = group.Name + ":" + name;
- var prop = new GroupedDotvvmProperty(name, group) {
+ var prop = new GroupedDotvvmProperty(name, id, group) {
PropertyType = group.PropertyType,
- DeclaringType = group.DeclaringType,
DefaultValue = group.DefaultValue,
IsValueInherited = false,
- Name = propname,
ObsoleteAttribute = group.ObsoleteAttribute,
OwningCapability = group.OwningCapability,
UsedInCapabilities = group.UsedInCapabilities
};
- DotvvmProperty.InitializeProperty(prop, group.AttributeProvider);
+ DotvvmProperty.InitializeProperty(prop, group.AttributeProvider); // TODO: maybe inline and specialize to just copy the group attributes
return prop;
}
}
diff --git a/src/Framework/Framework/Binding/ValueOrBinding.cs b/src/Framework/Framework/Binding/ValueOrBinding.cs
index bf06d6d635..58c0f87f43 100644
--- a/src/Framework/Framework/Binding/ValueOrBinding.cs
+++ b/src/Framework/Framework/Binding/ValueOrBinding.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using DotVVM.Framework.Binding.Expressions;
@@ -16,10 +17,13 @@ public interface ValueOrBinding
{
IBinding? BindingOrDefault { get; }
object? BoxedValue { get; }
+
+ /// Returns the value or the binding from the ValueOrBinding container. Equivalent to calling vob.BindingOrDefault ?? vob.BoxedValue
+ object? UnwrapToObject();
}
/// Represents either a binding or a constant value. In TypeScript this would be | . Note that `default()` is the same as `new (default(T))`
- public struct ValueOrBinding : ValueOrBinding
+ public readonly struct ValueOrBinding : ValueOrBinding
{
private readonly IBinding? binding;
[AllowNull]
@@ -50,6 +54,7 @@ public ValueOrBinding(IStaticValueBinding binding)
}
/// Creates new ValueOrBinding which contains the specified value. Note that there is an implicit conversion for this, so calling the constructor explicitly may be unnecessary.
+ [DebuggerStepThrough]
public ValueOrBinding(T value)
{
this.value = value;
@@ -91,6 +96,9 @@ public IBinding GetBinding() =>
public T GetValue() =>
HasValue ? value : throw new DotvvmControlException($"Value was expected but ValueOrBinding<{typeof(T).Name}> contains a binding: {binding}.") { RelatedBinding = binding };
+ /// Returns the value or the binding from the ValueOrBinding container. Equivalent to calling vob.BindingOrDefault ?? vob.BoxedValue
+ public object? UnwrapToObject() => binding ?? BoxingUtils.BoxGeneric(value);
+
/// Returns a ValueOrBinding with new type T which is a base type of the old T2
public static ValueOrBinding DownCast(ValueOrBinding createFrom)
where T2 : T => new ValueOrBinding(createFrom.binding, createFrom.value!);
@@ -190,6 +198,5 @@ public TResult ProcessValueBinding(DotvvmBindableObject control, Func
HasBinding ? binding.ToString() :
value is null ? "null" : value.ToString();
-
}
}
diff --git a/src/Framework/Framework/Binding/ValueOrBindingExtensions.cs b/src/Framework/Framework/Binding/ValueOrBindingExtensions.cs
index a822e42cdf..0ada556d88 100644
--- a/src/Framework/Framework/Binding/ValueOrBindingExtensions.cs
+++ b/src/Framework/Framework/Binding/ValueOrBindingExtensions.cs
@@ -15,9 +15,6 @@
public static class ValueOrBindingExtensions
{
- /// Returns the value or the binding from the ValueOrBinding container. Equivalent to calling vob.BindingOrDefault ?? vob.BoxedValue
- public static object? UnwrapToObject(this ValueOrBinding vob) =>
- vob.BindingOrDefault ?? vob.BoxedValue;
/// If the obj is ValueOrBinding, returns the binding or the value from the container. Equivalent to obj is ValueOrBinding vob ? vob.UnwrapToObject() : obj
public static object? UnwrapToObject(object? obj) =>
diff --git a/src/Framework/Framework/Binding/VirtualPropertyGroupDictionary.cs b/src/Framework/Framework/Binding/VirtualPropertyGroupDictionary.cs
index 8be168dd0e..213f5382ed 100644
--- a/src/Framework/Framework/Binding/VirtualPropertyGroupDictionary.cs
+++ b/src/Framework/Framework/Binding/VirtualPropertyGroupDictionary.cs
@@ -9,6 +9,12 @@
using DotVVM.Framework.Binding.Expressions;
using DotVVM.Framework.Utils;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Diagnostics;
+
+#if Vectorize
+using System.Runtime.Intrinsics;
+#endif
namespace DotVVM.Framework.Binding
{
@@ -16,92 +22,92 @@ namespace DotVVM.Framework.Binding
public readonly struct VirtualPropertyGroupDictionary : IDictionary, IReadOnlyDictionary
{
private readonly DotvvmBindableObject control;
- private readonly DotvvmPropertyGroup group;
+ private readonly ushort groupId;
+ private readonly bool isBindingProperty;
public VirtualPropertyGroupDictionary(DotvvmBindableObject control, DotvvmPropertyGroup group)
{
this.control = control;
- this.group = group;
+ this.groupId = group.Id;
+ this.isBindingProperty = group.IsBindingProperty;
}
- public IEnumerable Keys
+ internal VirtualPropertyGroupDictionary(DotvvmBindableObject control, ushort groupId, bool isBindingProperty)
{
- get
- {
- foreach (var (p, _) in control.properties)
- {
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- yield return pg.GroupMemberName;
- }
- }
- }
+ this.control = control;
+ this.groupId = groupId;
+ this.isBindingProperty = isBindingProperty;
}
- /// Lists all values. If any of the properties contains a binding, it will be automatically evaluated.
- public IEnumerable Values
+ public DotvvmBindableObject Control => control;
+ public ushort GroupId => groupId;
+ public DotvvmPropertyGroup Group => DotvvmPropertyIdAssignment.GetPropertyGroup(groupId).NotNull();
+
+ DotvvmPropertyId GetMemberId(string key, bool createNew = false)
{
- get
+ var memberId = DotvvmPropertyIdAssignment.GetGroupMemberId(key, registerIfNotFound: createNew);
+ return DotvvmPropertyId.CreatePropertyGroupId(groupId, memberId);
+ }
+
+ TValue EvalPropertyValue(object? value)
+ {
+ if (typeof(TValue).IsValueType)
+ { // => cheap type check
+ if (value is TValue val)
+ return val;
+ return EvalPropertyValueCore(value);
+ }
+ else
{
- foreach (var (p, _) in control.properties)
+ if (!isBindingProperty && value is IBinding binding)
{
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- yield return (TValue)control.GetValue(p)!;
- }
+ value = control.EvalBinding(binding, false);
}
+ return (TValue)value!;
}
}
+ TValue EvalPropertyValueCore(object? value) =>
+ (TValue)control.EvalPropertyValue(Group, value)!;
- public IEnumerable Properties
+ public IEnumerable Keys
{
get
{
- foreach (var (p, _) in control.properties)
+ foreach (var (p, _) in control.properties.PropertyGroup(groupId))
{
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- yield return pg;
- }
+ yield return DotvvmPropertyIdAssignment.GetGroupMemberName(p.MemberId)!;
}
}
}
- public int Count
+ /// Lists all values. If any of the properties contains a binding, it will be automatically evaluated.
+ public IEnumerable Values
{
get
{
- // we don't want to use Linq Enumerable.Count() as it would allocate
- // the enumerator. foreach gets the struct enumerator so it does not allocate anything
- var count = 0;
- foreach (var (p, _) in control.properties)
+ foreach (var (p, value) in control.properties.PropertyGroup(groupId))
{
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- count++;
- }
+ yield return EvalPropertyValue(value);
}
- return count;
}
}
- public bool Any()
+ public IEnumerable Properties
{
- foreach (var (p, _) in control.properties)
+ get
{
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
+ var group = DotvvmPropertyIdAssignment.GetPropertyGroup(groupId).NotNull();
+ foreach (var (p, _) in control.properties.PropertyGroup(groupId))
{
- return true;
+ yield return group.GetDotvvmProperty(p.MemberId);
}
}
- return false;
}
+ public int Count => control.properties.CountPropertyGroup(groupId);
+
+ public bool Any() => control.properties.ContainsPropertyGroup(groupId);
+
public bool IsReadOnly => false;
ICollection IDictionary.Keys => Keys.ToList();
@@ -113,77 +119,94 @@ public TValue this[string key]
{
get
{
- var p = group.GetDotvvmProperty(key);
+ var p = GetMemberId(key);
if (control.properties.TryGet(p, out var value))
- return (TValue)control.EvalPropertyValue(p, value)!;
+ return EvalPropertyValue(value);
else
- return (TValue)p.DefaultValue!;
- }
- set
- {
- control.properties.Set(group.GetDotvvmProperty(key), value);
+ return (TValue)Group.DefaultValue!;
}
+ set => control.properties.Set(GetMemberId(key, createNew: true), value);
}
/// Gets the value binding set to a specified property. Returns null if the property is not a binding, throws if the binding some kind of command.
- public IValueBinding? GetValueBinding(string key) => control.GetValueBinding(group.GetDotvvmProperty(key));
+ public IValueBinding? GetValueBinding(string key)
+ {
+ var binding = GetBinding(key);
+ if (binding != null && binding is not IStaticValueBinding) // throw exception on incompatible binding types
+ {
+ throw new BindingHelper.BindingNotSupportedException(binding) { RelatedControl = control };
+ }
+ return binding as IValueBinding;
+
+ }
/// Gets the binding set to a specified property. Returns null if the property is not set or if the value is not a binding.
- public IBinding? GetBinding(string key) => control.GetBinding(group.GetDotvvmProperty(key));
+ public IBinding? GetBinding(string key) => GetValueRaw(key) as IBinding;
/// Gets the value or a binding object for a specified property.
public object? GetValueRaw(string key)
{
- var p = group.GetDotvvmProperty(key);
- if (control.properties.TryGet(p, out var value))
+ if (control.properties.TryGet(GetMemberId(key), out var value))
return value;
else
- return p.DefaultValue!;
+ return DotvvmPropertyIdAssignment.GetPropertyGroup(groupId)!.DefaultValue!;
}
/// Adds value or overwrites the property identified by .
public void Set(string key, ValueOrBinding value)
{
- control.properties.Set(group.GetDotvvmProperty(key), value.UnwrapToObject());
+ control.properties.Set(GetMemberId(key, createNew: true), value.UnwrapToObject());
}
/// Adds value or overwrites the property identified by with the value.
public void Set(string key, TValue value) =>
- control.properties.Set(group.GetDotvvmProperty(key), value);
+ control.properties.Set(GetMemberId(key, createNew: true), value);
/// Adds binding or overwrites the property identified by with the binding.
public void SetBinding(string key, IBinding binding) =>
- control.properties.Set(group.GetDotvvmProperty(key), binding);
+ control.properties.Set(GetMemberId(key, createNew: true), binding);
- public bool ContainsKey(string key)
+ internal void SetInternal(ushort key, object? value)
{
- return control.Properties.ContainsKey(group.GetDotvvmProperty(key));
+ Debug.Assert(DotvvmPropertyIdAssignment.GetGroupMemberName(key) is not null);
+ control.properties.Set(DotvvmPropertyId.CreatePropertyGroupId(groupId, key), value);
}
- private void AddOnConflict(GroupedDotvvmProperty property, object? value)
+ public bool ContainsKey(string key) =>
+ control.properties.Contains(GetMemberId(key));
+
+ private void AddOnConflict(DotvvmPropertyId id, string key, object? value)
{
- var merger = this.group.ValueMerger;
+ var merger = this.Group.ValueMerger;
if (merger is null)
- throw new ArgumentException($"Cannot Add({property.Name}, {value}) since the value is already set and merging is not enabled on this property group.");
- var mergedValue = merger.MergePlainValues(property, control.properties.GetOrThrow(property), value);
- control.properties.Set(property, mergedValue);
+ throw new ArgumentException($"Cannot Add({key}, {value}) since the value is already set and merging is not enabled on this property group.");
+ var mergedValue = merger.MergePlainValues(id, control.properties.GetOrThrow(id), value);
+ control.properties.Set(id, mergedValue);
}
+ internal void AddInternal(ushort key, object? val)
+ {
+ var prop = DotvvmPropertyId.CreatePropertyGroupId(groupId, key);
+ if (!control.properties.TryAdd(prop, val))
+ AddOnConflict(prop, prop.GroupMemberName.NotNull(), val);
+ }
/// Adds the property identified by . If the property is already set, it tries appending the value using the group's
public void Add(string key, ValueOrBinding value)
{
- var prop = group.GetDotvvmProperty(key);
+ var prop = GetMemberId(key, createNew: true);
object? val = value.UnwrapToObject();
if (!control.properties.TryAdd(prop, val))
- AddOnConflict(prop, val);
+ AddOnConflict(prop, key, val);
}
/// Adds the property identified by . If the property is already set, it tries appending the value using the group's
- public void Add(string key, TValue value) =>
- this.Add(key, new ValueOrBinding(value));
-
- /// Adds the property identified by . If the property is already set, it tries appending the value using the group's
- public void AddBinding(string key, IBinding? binding)
+ public void Add(string key, TValue value)
{
- Add(key, new ValueOrBinding(binding!));
+ var prop = GetMemberId(key, createNew: true);
+ var val = BoxingUtils.BoxGeneric(value);
+ if (!control.properties.TryAdd(prop, val))
+ AddOnConflict(prop, key, val);
}
+ /// Adds the property identified by . If the property is already set, it tries appending the value using the group's
+ public void AddBinding(string key, IBinding? binding) => Add(key, new ValueOrBinding(binding!));
+
public void CopyFrom(IEnumerable> values, bool clear = false)
{
if (clear) this.Clear();
@@ -201,38 +224,86 @@ public void CopyFrom(IEnumerable>> v
Set(item.Key, item.Value);
}
}
- public static IDictionary CreateValueDictionary(DotvvmBindableObject control, DotvvmPropertyGroup group)
+
+ public void CopyFrom(VirtualPropertyGroupDictionary values, bool clear = false)
+ where TValue2 : TValue
+ {
+ if (clear) this.Clear();
+ foreach (var (oldId, value) in values.control.properties.PropertyGroup(values.groupId))
+ {
+ var newId = DotvvmPropertyId.CreatePropertyGroupId(groupId, oldId.MemberId);
+ control.properties.Set(newId, value);
+ }
+ }
+
+ public static IDictionary CreateValueDictionary(DotvvmBindableObject control, DotvvmPropertyGroup group) =>
+ CreateValueDictionary(control, group.Id);
+
+ internal static IDictionary CreateValueDictionary(DotvvmBindableObject control, ushort groupId)
{
- var result = new Dictionary();
+ Dictionary result;
+#if Vectorize
+ // don't bother counting without vector instructions
+ if (Vector128.IsHardwareAccelerated)
+ {
+ var count = control.properties.CountPropertyGroup(groupId);
+ result = new(count);
+ if (count == 0)
+ return result;
+ }
+ else
+ result = new();
+#else
+ result = new();
+#endif
foreach (var (p, valueRaw) in control.properties)
{
- if (p is GroupedDotvvmProperty pg && pg.PropertyGroup == group)
+ if (p.IsInPropertyGroup(groupId))
{
- var valueObj = control.EvalPropertyValue(p, valueRaw);
+ var name = DotvvmPropertyIdAssignment.GetGroupMemberName(p.MemberId)!;
+ var valueObj = control.EvalPropertyValue(DotvvmPropertyId.CreatePropertyGroupId(groupId, 1), valueRaw);
if (valueObj is TValue value)
- result.Add(pg.GroupMemberName, value);
+ result.Add(name, value);
else if (valueObj is null)
- result.Add(pg.GroupMemberName, default!);
+ result.Add(name, default!);
}
}
return result;
}
- public static IDictionary> CreatePropertyDictionary(DotvvmBindableObject control, DotvvmPropertyGroup group)
+ public static IDictionary> CreatePropertyDictionary(DotvvmBindableObject control, DotvvmPropertyGroup group) =>
+ CreatePropertyDictionary(control, group.Id);
+
+ internal static IDictionary> CreatePropertyDictionary(DotvvmBindableObject control, ushort groupId)
{
- var result = new Dictionary>();
+ Dictionary> result;
+#if Vectorize
+ // don't bother counting without vector instructions
+ if (Vector128.IsHardwareAccelerated)
+ {
+ var count = control.properties.CountPropertyGroup(groupId);
+ result = new(count);
+ if (count == 0)
+ return result;
+ }
+ else
+ result = new();
+#else
+ result = new();
+#endif
foreach (var (p, valRaw) in control.properties)
{
- if (p is GroupedDotvvmProperty pg && pg.PropertyGroup == group)
+ if (p.IsInPropertyGroup(groupId))
{
- result.Add(pg.GroupMemberName, ValueOrBinding.FromBoxedValue(valRaw));
+ var name = DotvvmPropertyIdAssignment.GetGroupMemberName(p.MemberId)!;
+ result.Add(name, ValueOrBinding.FromBoxedValue(valRaw));
}
}
return result;
}
public bool Remove(string key)
{
- return control.Properties.Remove(group.GetDotvvmProperty(key));
+ return control.properties.Remove(GetMemberId(key));
}
/// Tries getting value of property identified by . If the property contains a binding, it will be automatically evaluated.
@@ -240,10 +311,11 @@ public bool Remove(string key)
public bool TryGetValue(string key, [MaybeNullWhen(false)] out TValue value)
#pragma warning restore CS8767
{
- var prop = group.GetDotvvmProperty(key);
- if (control.properties.TryGet(prop, out var valueRaw))
+ var memberId = DotvvmPropertyIdAssignment.GetGroupMemberId(key, registerIfNotFound: false);
+ var p = DotvvmPropertyId.CreatePropertyGroupId(groupId, memberId);
+ if (control.properties.TryGet(p, out var valueRaw))
{
- value = (TValue)control.EvalPropertyValue(prop, valueRaw)!;
+ value = EvalPropertyValue(valueRaw);
return true;
}
else
@@ -254,40 +326,9 @@ public bool TryGetValue(string key, [MaybeNullWhen(false)] out TValue value)
}
/// Adds the property-value pair to the dictionary. If the property is already set, it tries appending the value using the group's
- public void Add(KeyValuePair item)
- {
- Add(item.Key, item.Value);
- }
-
- public void Clear()
- {
- // we want to avoid allocating the list if there is only one property
- DotvvmProperty? toRemove = null;
- List? toRemoveRest = null;
-
- foreach (var (p, _) in control.properties)
- {
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
- {
- if (toRemove is null)
- toRemove = p;
- else
- {
- if (toRemoveRest is null)
- toRemoveRest = new List();
- toRemoveRest.Add(p);
- }
- }
- }
+ public void Add(KeyValuePair item) => Add(item.Key, item.Value);
- if (toRemove is {})
- control.Properties.Remove(toRemove);
-
- if (toRemoveRest is {})
- foreach (var p in toRemoveRest)
- control.Properties.Remove(p);
- }
+ public void Clear() => control.properties.ClearPropertyGroup(groupId);
public bool Contains(KeyValuePair item)
{
@@ -319,33 +360,101 @@ public bool Remove(KeyValuePair item)
}
/// Enumerates all keys and values. If a property contains a binding, it will be automatically evaluated.
- public IEnumerator> GetEnumerator()
+ public Enumerator GetEnumerator() =>
+ new Enumerator(control, Group, control.properties.EnumeratePropertyGroup(groupId));
+
+ IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ /// Enumerates all keys and values, without evaluating the bindings.
+ public RawValuesCollection RawValues => new RawValuesCollection(this);
+
+ public readonly struct RawValuesCollection: IEnumerable>, IReadOnlyDictionary
{
- foreach (var (p, value) in control.properties)
+ readonly VirtualPropertyGroupDictionary self;
+
+ internal RawValuesCollection(VirtualPropertyGroupDictionary self)
{
- var pg = p as GroupedDotvvmProperty;
- if (pg != null && pg.PropertyGroup == group)
+ this.self = self;
+ }
+
+ public object? this[string key] => self.GetValueRaw(key);
+ public bool TryGetValue(string key, [MaybeNullWhen(false)] out object? value) =>
+ self.control.properties.TryGet(self.GetMemberId(key), out value);
+
+ public IEnumerable Keys => self.Keys;
+
+ public IEnumerable