mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-18 22:55:08 +08:00
dbc09b8e22
# Description Adopt [`System.TimeProvider`](https://learn.microsoft.com/dotnet/api/system.timeprovider) across the stack so that every place that creates timers, runs duration math, or schedules periodic work is mockable, deterministic in tests, and immune to wall-clock changes. The migration touches Core, Core.Types, Server, Client, PubSub, Configuration, Gds (server + client), Lds, and the Quickstarts/Console reference apps. Per-component changes follow the same pattern: 1. Add a `private readonly TimeProvider m_timeProvider;` field. 2. Accept a nullable `TimeProvider? timeProvider = null` as the **last** constructor parameter (new overload; legacy constructor stays for binary compatibility and delegates with `timeProvider: null`). 3. Initialise the field with `m_timeProvider = timeProvider ??= TimeProvider.System;`. 4. Replace `new Timer(...)`, `Stopwatch`, `HiResClock.*`, `DateTime.UtcNow`-based duration math, and `Task.Delay(delay, ct)` in timing loops with the matching `m_timeProvider` API (`CreateTimer`, `GetTimestamp` / `GetElapsedTime`, `GetUtcNow().UtcDateTime`, `Task.Delay(delay, m_timeProvider, ct)`). 5. Wire the provider through DI: `AddOpcUaServerBuilder` / `AddOpcUaClientBuilder` register `TimeProvider.System` via `TryAddSingleton<TimeProvider>()` so tests can swap in `Microsoft.Extensions.Time.Testing.FakeTimeProvider`. `HiResClock` itself is kept (so the `ApplicationConfiguration.DisableHiResClock` schema flag still round-trips), but **every public member** is now `[Obsolete]` with a single shared message pointing to the new migration-guide section. The few internal call sites that still need to set/read `HiResClock` are wrapped with `#pragma warning disable CS0618`. ### Highlights per component - **Core / Stack** — `Opc.Ua.Core`, `Opc.Ua.Core.Types`, channel pipeline (`UaSCBinaryClientChannel`, `ChannelToken`, `UaSCBinaryChannel`), `ApplicationConfiguration`, certificate validators, `JwksIssuerKeyResolver`, `TimeProviderExtensions` polyfills (`GetTimestampMilliseconds`, `GetTickCount`, `CancellationTokenSource(timeout, timeProvider)`, `Task.Delay(delay, provider, ct)` polyfill for older TFMs). - **Server** — session lockout, subscription pipeline (publish/sample/keep-alive/lifetime), `MonitoredItemQueue`, alarm sampling rate, `IntervalRunner`, server diagnostics, configuration state machines, hosted services. - **Client** — `Session`, managed/V2 subscription engines, reconnect handlers, channel keep-alive, periodic publishing, complex-type discovery polling. - **PubSub** — `IntervalRunner` pipeline, message-receive timers, MQTT/UDP keep-alive, publishers/readers. - **Gds / Lds** — `LdsServer`, `LinqApplicationsDatabase` expiry housekeeping, GDS clients (push, pull), `DirectoryCertificateStore`, `ConnectionStateMachine`, classic subscription engine. - **Quickstarts / Console samples** — `SampleNodeManager`, `ReferenceNodeManager`, `AlarmNodeManager`, `BoilerState`, `MemoryBufferState`, `PublishedValuesWrites`. Samples resolve via `(Server as ITimeProviderProvider)?.TimeProvider ?? TimeProvider.System` so existing ctor signatures stay binary-compatible. - **Tests** — `Opc.Ua.PubSub.Tests` adds `IntervalRunnerTests`; existing `SubscriptionTests`, `UaPublisherTests`, and `TestDataSystem` move off `HiResClock`. Added `Microsoft.Extensions.TimeProvider.Testing` package for `FakeTimeProvider` (project-load warning surfaces on net48/net472 only — runtime is .NET 8+). ### Migration guide New section [**Time and Timer abstraction (`TimeProvider`)**](Docs/MigrationGuide.md#time-and-timer-abstraction-timeprovider) documents: - The canonical replacement table (`HiResClock.UtcNow` → `timeProvider.GetUtcNow().UtcDateTime`, `HiResClock.TickCount64` → `timeProvider.GetTimestamp()`, `new Timer` → `timeProvider.CreateTimer`, `Task.Delay(delay, ct)` → `Task.Delay(delay, timeProvider, ct)`, …). - The `TimeProvider? timeProvider = null` + `??=` constructor pattern. - DI wiring and how to swap in `FakeTimeProvider` in tests. - Before/after snippets for `HiResClock.TickCount64`, `HiResClock.UtcNow`, and `new Timer(...)`. ## Related Issues - Fixes #2458 ## Checklist - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received.
527 lines
21 KiB
C#
527 lines
21 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
|
|
*
|
|
* OPC Foundation MIT License 1.00
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* The complete license agreement can be found here:
|
|
* http://opcfoundation.org/License/MIT/1.00/
|
|
* ======================================================================*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using Microsoft.Extensions.Logging;
|
|
using Opc.Ua;
|
|
using Opc.Ua.PubSub;
|
|
|
|
namespace Quickstarts.ConsoleReferencePublisher
|
|
{
|
|
internal sealed class PublishedValuesWrites : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// It should match the namespace index from configuration file
|
|
/// </summary>
|
|
public const ushort NamespaceIndexSimple = 2;
|
|
public const ushort NamespaceIndexAllTypes = 3;
|
|
|
|
private const string kDataSetNameSimple = "Simple";
|
|
private const string kDataSetNameAllTypes = "AllTypes";
|
|
|
|
/// <summary>
|
|
/// simulate for BoolToogle changes to 3 seconds
|
|
/// </summary>
|
|
private int m_boolToogleCount;
|
|
private const int kBoolToogleLimit = 2;
|
|
private const int kSimpleInt32Limit = 10000;
|
|
|
|
private readonly List<FieldMetaData> m_simpleFields = [];
|
|
private readonly List<FieldMetaData> m_allTypesFields = [];
|
|
|
|
private readonly ILogger m_logger;
|
|
private readonly ArrayOf<PublishedDataSetDataType> m_publishedDataSets;
|
|
private readonly IUaPubSubDataStore m_dataStore;
|
|
private readonly TimeProvider m_timeProvider;
|
|
private ITimer m_updateValuesTimer = null!;
|
|
|
|
private readonly string[] m_aviationAlphabet =
|
|
[
|
|
"Alfa",
|
|
"Bravo",
|
|
"Charlie",
|
|
"Delta",
|
|
"Echo",
|
|
"Foxtrot",
|
|
"Golf",
|
|
"Hotel",
|
|
"India",
|
|
"Juliet",
|
|
"Kilo",
|
|
"Lima",
|
|
"Mike",
|
|
"November",
|
|
"Oscar",
|
|
"Papa",
|
|
"Quebec",
|
|
"Romeo",
|
|
"Sierra",
|
|
"Tango",
|
|
"Uniform",
|
|
"Victor",
|
|
"Whiskey",
|
|
"X-Ray",
|
|
"Yankee",
|
|
"Zulu"
|
|
];
|
|
|
|
private int m_aviationAlphabetIndex;
|
|
private readonly Lock m_lock = new();
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="uaPubSubApplication"></param>
|
|
/// <param name="telemetry"></param>
|
|
/// <param name="timeProvider">
|
|
/// Optional time provider. Defaults to <see cref="TimeProvider.System"/>.
|
|
/// </param>
|
|
public PublishedValuesWrites(
|
|
UaPubSubApplication uaPubSubApplication,
|
|
ITelemetryContext telemetry,
|
|
TimeProvider? timeProvider = null)
|
|
{
|
|
m_logger = telemetry.CreateLogger<PublishedValuesWrites>();
|
|
m_publishedDataSets = uaPubSubApplication.UaPubSubConfigurator.PubSubConfiguration
|
|
.PublishedDataSets;
|
|
m_dataStore = uaPubSubApplication.DataStore;
|
|
m_timeProvider = timeProvider ?? TimeProvider.System;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
m_updateValuesTimer.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize PublisherData with information from configuration and start timer to update data
|
|
/// </summary>
|
|
public void Start()
|
|
{
|
|
if (!m_publishedDataSets.IsNull)
|
|
{
|
|
// Remember the fields to be updated
|
|
foreach (PublishedDataSetDataType publishedDataSet in m_publishedDataSets)
|
|
{
|
|
switch (publishedDataSet.Name)
|
|
{
|
|
case kDataSetNameSimple:
|
|
m_simpleFields.AddRange(publishedDataSet.DataSetMetaData.Fields);
|
|
break;
|
|
case kDataSetNameAllTypes:
|
|
m_allTypesFields.AddRange(publishedDataSet.DataSetMetaData.Fields);
|
|
break;
|
|
default:
|
|
m_logger.LogInformation(
|
|
"PublishedValuesWrites.Start: {DataSet} unknown.",
|
|
publishedDataSet.Name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
LoadInitialData();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_logger.LogError(e,
|
|
"SamplePublisher.DataStoreValuesGenerator.LoadInitialData wrong field");
|
|
}
|
|
|
|
m_updateValuesTimer = m_timeProvider.CreateTimer(
|
|
UpdateValues,
|
|
null,
|
|
TimeSpan.FromSeconds(1),
|
|
TimeSpan.FromSeconds(1));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load initial demo data
|
|
/// </summary>
|
|
private void LoadInitialData()
|
|
{
|
|
WriteFieldData(
|
|
"BoolToggle",
|
|
NamespaceIndexSimple,
|
|
new DataValue(new Variant(false), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"Int32",
|
|
NamespaceIndexSimple,
|
|
new DataValue(new Variant(0), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"Int32Fast",
|
|
NamespaceIndexSimple,
|
|
new DataValue(new Variant(0), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"DateTime",
|
|
NamespaceIndexSimple,
|
|
new DataValue(new Variant(DateTime.UtcNow), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
|
|
WriteFieldData(
|
|
"BoolToggle",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant(true), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"Byte",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant((byte)0), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"Int16",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant((short)0), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"Int32",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant(0), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"SByte",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant((sbyte)0), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"UInt16",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant((ushort)0), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"UInt32",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant((uint)0), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"UInt64",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant((ulong)0), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"Float",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant((float)0F), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"Double",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant((double)0.0), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"String",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant(m_aviationAlphabet[0]), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"ByteString",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(
|
|
new Variant(ByteString.From([1, 2, 3])),
|
|
StatusCodes.Good,
|
|
DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"Guid",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant(Uuid.NewUuid()), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"DateTime",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(new Variant(DateTime.UtcNow), StatusCodes.Good, DateTime.UtcNow)
|
|
);
|
|
WriteFieldData(
|
|
"UInt32Array",
|
|
NamespaceIndexAllTypes,
|
|
new DataValue(
|
|
new Variant(new uint[] { 1, 2, 3 }),
|
|
StatusCodes.Good,
|
|
DateTime.UtcNow)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write (update) field data
|
|
/// </summary>
|
|
/// <param name="metaDatafieldName"></param>
|
|
/// <param name="dataValue"></param>
|
|
private void WriteFieldData(
|
|
string metaDatafieldName,
|
|
ushort namespaceIndex,
|
|
DataValue dataValue)
|
|
{
|
|
m_dataStore.WritePublishedDataItem(
|
|
new NodeId(metaDatafieldName, namespaceIndex),
|
|
Attributes.Value,
|
|
dataValue
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simulate value changes in dynamic nodes
|
|
/// </summary>
|
|
/// <param name="state"></param>
|
|
private void UpdateValues(object? state)
|
|
{
|
|
try
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
foreach (FieldMetaData variable in m_simpleFields)
|
|
{
|
|
switch (variable.Name)
|
|
{
|
|
case "BoolToggle":
|
|
m_boolToogleCount++;
|
|
if (m_boolToogleCount >= kBoolToogleLimit)
|
|
{
|
|
m_boolToogleCount = 0;
|
|
IncrementValue(variable, NamespaceIndexSimple);
|
|
}
|
|
break;
|
|
case "Int32":
|
|
IncrementValue(variable, NamespaceIndexSimple, kSimpleInt32Limit);
|
|
break;
|
|
case "Int32Fast":
|
|
IncrementValue(
|
|
variable,
|
|
NamespaceIndexSimple,
|
|
kSimpleInt32Limit,
|
|
100);
|
|
break;
|
|
case "DateTime":
|
|
IncrementValue(variable, NamespaceIndexSimple);
|
|
break;
|
|
default:
|
|
m_logger.LogDebug("{Variable} not processed.", variable.Name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (FieldMetaData variable in m_allTypesFields)
|
|
{
|
|
IncrementValue(variable, NamespaceIndexAllTypes);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_logger.LogError(e, "Unexpected error doing simulation.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Increment value
|
|
/// maxAllowedValue - maximum incremented value before reset value to initial value
|
|
/// step - the increment value
|
|
/// </summary>
|
|
/// <param name="variable"></param>
|
|
/// <param name="namespaceIndex"></param>
|
|
/// <param name="maxAllowedValue"></param>
|
|
/// <param name="step"></param>
|
|
/// <exception cref="ServiceResultException"></exception>
|
|
private void IncrementValue(
|
|
FieldMetaData variable,
|
|
ushort namespaceIndex,
|
|
long maxAllowedValue = int.MaxValue,
|
|
int step = 0
|
|
)
|
|
{
|
|
// Read value to be incremented
|
|
if (!m_dataStore.TryReadPublishedDataItem(
|
|
new NodeId(variable.Name!, namespaceIndex),
|
|
Attributes.Value,
|
|
out DataValue dataValue) ||
|
|
dataValue.WrappedValue.IsNull)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool valueUpdated = false;
|
|
|
|
BuiltInType builtInType = TypeInfo.GetBuiltInType(variable.DataType);
|
|
switch (builtInType)
|
|
{
|
|
case BuiltInType.Boolean:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
bool boolValue = (bool)dataValue.WrappedValue.ConvertToBoolean();
|
|
dataValue = dataValue.WithWrappedValue(!boolValue);
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.Byte:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
byte byteValue = (byte)dataValue.WrappedValue.ConvertToByte();
|
|
dataValue = dataValue.WithWrappedValue((byte)(byteValue + 1));
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.Int16:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
int intIdentifier = (short)dataValue.WrappedValue.ConvertToInt16();
|
|
Interlocked.CompareExchange(ref intIdentifier, 0, short.MaxValue);
|
|
dataValue = dataValue.WithWrappedValue((short)Interlocked.Increment(ref intIdentifier));
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.Int32:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
int int32Value = (int)dataValue.WrappedValue.ConvertToInt32();
|
|
if (step > 0)
|
|
{
|
|
int32Value += step - 1;
|
|
}
|
|
if (int32Value > maxAllowedValue)
|
|
{
|
|
int32Value = 0;
|
|
}
|
|
dataValue = dataValue.WithWrappedValue(Interlocked.Increment(ref int32Value));
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.SByte:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
int intIdentifier = (sbyte)dataValue.WrappedValue.ConvertToSByte();
|
|
Interlocked.CompareExchange(ref intIdentifier, 0, sbyte.MaxValue);
|
|
dataValue = dataValue.WithWrappedValue((sbyte)Interlocked.Increment(ref intIdentifier));
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.UInt16:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
int intIdentifier = dataValue.WrappedValue.ConvertToUInt16().GetUInt16();
|
|
Interlocked.CompareExchange(ref intIdentifier, 0, ushort.MaxValue);
|
|
dataValue = dataValue.WithWrappedValue((ushort)Interlocked.Increment(ref intIdentifier));
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.UInt32:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
long longIdentifier = dataValue.WrappedValue.ConvertToUInt32().GetUInt32();
|
|
Interlocked.CompareExchange(ref longIdentifier, 0, uint.MaxValue);
|
|
dataValue = dataValue.WithWrappedValue((uint)Interlocked.Increment(ref longIdentifier));
|
|
valueUpdated = true;
|
|
}
|
|
else if (variable.ValueRank == ValueRanks.OneDimension)
|
|
{
|
|
if (dataValue.WrappedValue.TryGetValue(out ArrayOf<uint> values))
|
|
{
|
|
var valuesList = values.ToList();
|
|
for (int i = 0; i < values.Count; i++)
|
|
{
|
|
long longIdentifier = values[i];
|
|
Interlocked.CompareExchange(ref longIdentifier, 0, uint.MaxValue);
|
|
valuesList[i] = (uint)Interlocked.Increment(ref longIdentifier);
|
|
}
|
|
dataValue = dataValue.WithWrappedValue(valuesList.ToArrayOf());
|
|
valueUpdated = true;
|
|
}
|
|
}
|
|
break;
|
|
case BuiltInType.UInt64:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
ulong uint64Value = (ulong)dataValue.WrappedValue.ConvertToUInt64();
|
|
float longIdentifier = uint64Value + 1;
|
|
Interlocked.CompareExchange(ref longIdentifier, 0, ulong.MaxValue);
|
|
dataValue = dataValue.WithWrappedValue((ulong)longIdentifier);
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.Float:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
float floatValue = (float)dataValue.WrappedValue.ConvertToFloat();
|
|
Interlocked.CompareExchange(ref floatValue, 0, float.MaxValue);
|
|
dataValue = dataValue.WithWrappedValue(floatValue + 1);
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.Double:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
double doubleValue = (double)dataValue.WrappedValue.ConvertToDouble();
|
|
Interlocked.CompareExchange(ref doubleValue, 0, double.MaxValue);
|
|
dataValue = dataValue.WithWrappedValue(doubleValue + 1);
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.DateTime:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
dataValue = dataValue.WithWrappedValue(DateTimeUtc.Now);
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.Guid:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
dataValue = dataValue.WithWrappedValue(Uuid.NewUuid());
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case BuiltInType.String:
|
|
if (variable.ValueRank == ValueRanks.Scalar)
|
|
{
|
|
m_aviationAlphabetIndex = (m_aviationAlphabetIndex + 1) %
|
|
m_aviationAlphabet.Length;
|
|
dataValue = dataValue.WithWrappedValue(m_aviationAlphabet[m_aviationAlphabetIndex]);
|
|
valueUpdated = true;
|
|
}
|
|
break;
|
|
case >= BuiltInType.Null and <= BuiltInType.Enumeration:
|
|
break;
|
|
default:
|
|
throw ServiceResultException.Unexpected($"Unexpected BuiltInType {builtInType}");
|
|
}
|
|
|
|
if (valueUpdated)
|
|
{
|
|
// Save new updated value to data store
|
|
WriteFieldData(variable.Name!, namespaceIndex, dataValue);
|
|
}
|
|
}
|
|
}
|
|
}
|