< Summary - pva.SuperV

Information
Class: pva.SuperV.Engine.RunnableProject
Assembly: pva.SuperV.Engine
File(s): /home/runner/work/pva.SuperV/pva.SuperV/pva.SuperV.Engine/RunnableProject.cs
Tag: dotnet-ubuntu_22190969454
Line coverage
95%
Covered lines: 260
Uncovered lines: 11
Coverable lines: 271
Total lines: 476
Line coverage: 95.9%
Branch coverage
84%
Covered branches: 49
Total branches: 58
Branch coverage: 84.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/pva.SuperV/pva.SuperV/pva.SuperV.Engine/RunnableProject.cs

#LineLine coverage
 1using pva.Helpers.Extensions;
 2using pva.SuperV.Common;
 3using pva.SuperV.Engine.Exceptions;
 4using pva.SuperV.Engine.HistoryRetrieval;
 5using pva.SuperV.Engine.HistoryStorage;
 6using pva.SuperV.Engine.Processing;
 7using System.Reflection;
 8using System.Runtime.CompilerServices;
 9using System.Text;
 10using System.Text.Json.Serialization;
 11
 12namespace pva.SuperV.Engine
 13{
 14    /// <summary>
 15    /// A runnable project. It allows to create new instances. However its definitions are fixed and can't be changed.
 16    /// </summary>
 17    /// <seealso cref="pva.SuperV.Engine.Project" />
 18    public class RunnableProject : Project
 19    {
 20        /// <summary>
 21        /// The project assembly loader.
 22        /// </summary>
 23        [JsonIgnore]
 24        private ProjectAssemblyLoader? projectAssemblyLoader;
 25
 26        [JsonIgnore]
 27        public WeakReference? ProjectAssemblyLoaderWeakRef
 28        {
 1529            get => projectAssemblyLoader == null
 1530                ? null
 1531                : new WeakReference(projectAssemblyLoader, trackResurrection: true);
 32        }
 33
 34        /// <summary>
 35        /// Gets the instances.
 36        /// </summary>
 37        /// <value>
 38        /// The instances.
 39        /// </value>
 40        [JsonIgnore]
 100141        public Dictionary<string, Instance> Instances { get; } = new(StringComparer.OrdinalIgnoreCase);
 42
 43        /// <summary>
 44        /// Initializes a new instance of the <see cref="RunnableProject"/> class.
 45        /// </summary>
 1346        public RunnableProject()
 1347        {
 1348        }
 49
 50        /// <summary>
 51        /// Initializes a new instance of the <see cref="RunnableProject"/> class from a <see cref="WipProject"/>.
 52        /// </summary>
 53        /// <param name="wipProject">The wip project.</param>
 13454        public RunnableProject(WipProject wipProject)
 13455        {
 13456            Name = wipProject.Name;
 13457            Version = wipProject.Version;
 13458            Classes = new(wipProject.Classes);
 13459            FieldFormatters = new(wipProject.FieldFormatters);
 13460            HistoryStorageEngineConnectionString = wipProject.HistoryStorageEngineConnectionString;
 13461            HistoryStorageEngine = wipProject.HistoryStorageEngine;
 13462            HistoryRepositories = new(wipProject.HistoryRepositories);
 13463            TopicsChannels = new(wipProject.TopicsChannels);
 13464            ScriptDefinitions = new(wipProject.ScriptDefinitions);
 13465            CreateHistoryRepositories(HistoryStorageEngine);
 13366            CreateHistoryClassTimeSeries();
 13267            SetupProjectAssemblyLoader();
 13268            RecreateInstances(wipProject);
 13269            RegisterScripts();
 13270        }
 71
 72        /// <summary>
 73        /// Registers the scripts.
 74        /// </summary>
 75        private void RegisterScripts()
 13276        {
 13277            ScriptDefinitions.Values.ForEach(scriptDefinition =>
 24778                CreateAndRegisterScript(scriptDefinition));
 13279        }
 80
 81        /// <summary>
 82        /// Creates and registers a script for field value changes.
 83        /// </summary>
 84        /// <param name="scriptDefinition">The script definition to register.</param>
 85        private void CreateAndRegisterScript(ScriptDefinition scriptDefinition)
 11586        {
 11587            Type? scriptType = GetType($"{scriptDefinition.Name}Class");
 11588            ScriptBase script = CreateScript(scriptType!, this, scriptDefinition);
 23089            Task.Run(async () => await script.RegisterForTopicNotification().AsTask());
 11590        }
 91
 92        public override string GetId()
 74293        {
 74294            return $"{Name!}";
 74295        }
 96
 97        /// <summary>
 98        /// Setups the project assembly loader. Compiles the project classes and scripts and generates an asse;bly
 99        /// </summary>
 100        private void SetupProjectAssemblyLoader()
 210101        {
 210102            if (projectAssemblyLoader == null)
 144103            {
 288104                Task.Run(async () => await ProjectBuilder.BuildAsync(this)).Wait();
 144105                projectAssemblyLoader ??= new();
 144106                projectAssemblyLoader.LoadFromAssemblyPath(GetAssemblyFileName());
 144107            }
 210108        }
 109
 110        /// <summary>
 111        /// Creates the history class time series.
 112        /// </summary>
 113        private void CreateHistoryClassTimeSeries()
 133114        {
 133115            Classes.Values.ForEach(clazz =>
 482116            {
 482117                clazz.FieldDefinitions.Values.ForEach(fieldDefinition =>
 2637118                {
 2637119                    fieldDefinition.ValuePostChangeProcessings
 2637120                        .OfType<IHistorizationProcessing>()
 2637121                        .ForEach(hp =>
 4272122                            hp.UpsertInHistoryStorage(Name!, clazz.Name!));
 3118123                });
 614124            });
 132125        }
 126
 127        /// <summary>
 128        /// Creates the history repositories.
 129        /// </summary>
 130        /// <param name="historyStorageEngine">The history storage engine.</param>
 131        /// <exception cref="NoHistoryStorageEngineException"></exception>
 132        private void CreateHistoryRepositories(IHistoryStorageEngine? historyStorageEngine)
 134133        {
 134134            if (HistoryRepositories.Keys.Count == 0)
 14135            {
 14136                return;
 137            }
 120138            if (historyStorageEngine is null)
 1139            {
 1140                throw new NoHistoryStorageEngineException(Name);
 141            }
 142
 119143            HistoryRepositories.Values.ForEach(repository =>
 119144            {
 119145                repository.HistoryStorageEngine = historyStorageEngine;
 119146                repository.UpsertRepository(Name!, historyStorageEngine);
 238147            });
 133148        }
 149
 150        /// <summary>
 151        /// Creates an instance.
 152        /// </summary>
 153        /// <param name="className">Name of the class.</param>
 154        /// <param name="instanceName">Name of the instance.</param>
 155        /// <param name="addToRunningInstances">Indicates if the created instances should be added to running instances 
 156        /// <returns>The newly created <see cref="Instance"/>.</returns>
 157        /// <exception cref="EntityAlreadyExistException"></exception>
 158        public Instance? CreateInstance(string className, string instanceName, bool addToRunningInstances = true)
 78159        {
 78160            SetupProjectAssemblyLoader();
 78161            if (Instances.ContainsKey(instanceName))
 1162            {
 1163                throw new EntityAlreadyExistException("Instance", instanceName);
 164            }
 165
 77166            Class clazz = GetClass(className);
 77167            Type? classType = GetType(clazz.Name);
 168
 77169            Instance? instance = CreateInstance(classType!);
 77170            if (instance is null)
 0171            {
 0172                return instance;
 173            }
 77174            instance.Name = instanceName;
 77175            instance.Class = clazz;
 77176            Class? currentClass = clazz;
 184177            while (currentClass is not null)
 107178            {
 107179                currentClass.FieldDefinitions.ForEach((k, v) =>
 559180                {
 559181                    instance!.Fields.TryGetValue(k, out IField? field);
 559182                    field!.FieldDefinition = v;
 666183                });
 107184                currentClass = currentClass.BaseClass;
 107185            }
 77186            if (addToRunningInstances)
 77187            {
 77188                Instances.Add(instanceName, instance);
 77189            }
 77190            return instance;
 77191        }
 192
 193        /// <summary>
 194        /// Gets the type for a name in project assembly.
 195        /// </summary>
 196        /// <param name="name">The name of the type.</param>
 197        /// <returns>The type</returns>
 198        private Type? GetType(string name)
 192199        {
 192200            string typeFullName = $"{GetNamespace()}.{name}";
 192201            return projectAssemblyLoader!.Assemblies.First()?.GetType(typeFullName!);
 192202        }
 203
 204        /// <summary>
 205        /// Creates an instance for targetType's <see cref="Class"/>.
 206        /// </summary>
 207        /// <param name="targetType">Type of the target.</param>
 208        /// <returns>Created <see cref="Instance"/>.</returns>
 209        private static Instance CreateInstance(Type targetType)
 77210        {
 77211            var ctor = GetConstructor(targetType);
 77212            return (Instance)ctor.Invoke([]);
 77213        }
 214
 215        /// <summary>
 216        /// Creates an instance for targetType's <see cref="ScriptBase"/>.
 217        /// </summary>
 218        /// <param name="targetType">Type of the target.</param>
 219        /// <param name="project">Project in which to create script instance.</param>
 220        /// <param name="scriptDefinition">Script definitoin of script.</param>
 221        /// <returns>Created <see cref="ScriptBase"/>.</returns>
 222        private static ScriptBase CreateScript(Type targetType, RunnableProject project, ScriptDefinition scriptDefiniti
 115223        {
 115224            var ctor = GetConstructor(targetType, [typeof(RunnableProject), typeof(ScriptDefinition)]);
 115225            return (ScriptBase)ctor.Invoke([project, scriptDefinition]);
 115226        }
 227
 228        /// <summary>
 229        /// Gets the empty constructor for targetType.
 230        /// </summary>
 231        /// <param name="targetType">Type of the target.</param>
 232        /// <returns>Constructor info of the type.</returns>
 233        /// <exception cref="InvalidOperationException">No constructor found for targetType.</exception>
 234        private static ConstructorInfo GetConstructor(Type targetType)
 77235        {
 77236            return GetConstructor(targetType, Type.EmptyTypes);
 77237        }
 238
 239        /// <summary>
 240        /// Gets the empty constructor for targetType.
 241        /// </summary>
 242        /// <param name="targetType">Type of the target.</param>
 243        /// <param name="ctorArgTypes">Types for constructor arguments</param>
 244        /// <returns>Constructor info of the type.</returns>
 245        /// <exception cref="InvalidOperationException">No constructor found for targetType.</exception>
 246        private static ConstructorInfo GetConstructor(Type targetType, Type[] ctorArgTypes)
 192247        {
 192248            return targetType.GetConstructor(ctorArgTypes)
 192249                ?? throw new InvalidOperationException($"No constructor found for {targetType.Name}.");
 192250        }
 251
 252        /// <summary>
 253        /// Removes an instance by its name.
 254        /// </summary>
 255        /// <param name="instanceName">Name of the instance.</param>
 256        public void RemoveInstance(string instanceName)
 1257        {
 1258            Instances.Remove(instanceName);
 1259        }
 260
 261        /// <summary>
 262        /// Gets an instance by its name.
 263        /// </summary>
 264        /// <param name="instanceName">Name of the instance.</param>
 265        /// <returns>The <see cref="Instance"/></returns>
 266        /// <exception cref="UnknownEntityException">Indicates that entity wasn't found.</exception>
 267        public Instance GetInstance(string instanceName)
 294268        {
 294269            if (Instances.TryGetValue(instanceName, out var instance))
 293270            {
 293271                return instance;
 272            }
 273
 1274            throw new UnknownEntityException("Instance", instanceName);
 293275        }
 276
 277        /// <summary>
 278        /// Recreates the instances from a <see cref="WipProject"/>.
 279        /// </summary>
 280        /// <param name="wipProject">The wip project.</param>
 281        private void RecreateInstances(WipProject wipProject)
 132282        {
 132283            wipProject.ToLoadInstances
 132284                .ForEach((k, v) =>
 2285                {
 2286                    string instanceName = k;
 2287                    Instance oldInstance = v;
 2288                    Instance? newInstance = CreateInstance(oldInstance.Class.Name, instanceName);
 2289                    Dictionary<string, IField> newFields = new(newInstance!.Fields.Count);
 2290                    newInstance.Fields
 2291                        .ForEach((k1, v2) =>
 14292                        {
 14293                            string fieldName = k1;
 14294                            newFields.Add(fieldName,
 14295                                oldInstance.Fields.GetValueOrDefault(fieldName, v2));
 16296                        });
 2297                    newInstance.Fields = newFields;
 134298                });
 132299        }
 300
 301        /// <summary>
 302        /// Unloads the project. Clears all instances.
 303        /// </summary>
 304        [MethodImpl(MethodImplOptions.NoInlining)]
 305        public override void Unload()
 143306        {
 143307            Instances.Values.ForEach(instance =>
 216308                instance.Dispose());
 143309            Instances.Clear();
 143310            HistoryStorageEngine?.Dispose();
 143311            TopicsChannels.Values.ForEach(topicChannel =>
 377312                topicChannel.Writer.TryComplete());
 143313            TopicsChannels.Clear();
 143314            base.Unload();
 143315            Projects.Remove(GetId(), out _);
 143316            projectAssemblyLoader?.Unload();
 143317            projectAssemblyLoader = null;
 143318        }
 319
 320        /// <summary>
 321        /// Gets the C# code for generating the project's assembly with <see cref="Project.BuildAsync(WipProject)"/>.
 322        /// </summary>
 323        /// <returns>C# code.</returns>
 324        public string GetCode()
 132325        {
 132326            StringBuilder codeBuilder = new();
 132327            codeBuilder = codeBuilder
 132328                .AppendLine("using System.Collections.Generic;")
 132329                .AppendLine("using System.Reflection;")
 132330                .AppendLine("using System.Threading.Tasks;")
 132331                .AppendLine($"using {GetType().Namespace};")
 132332                .AppendLine("using pva.SuperV.Engine.Processing;")
 132333                .AppendLine($"[assembly: AssemblyProduct(\"{Name}\")]")
 132334                .AppendLine($"[assembly: AssemblyTitle(\"{Description}\")]")
 132335                .AppendLine($"[assembly: AssemblyVersion(\"{Version}\")]")
 132336                .AppendLine($"[assembly: AssemblyFileVersion(\"{Version}\")]")
 132337                .AppendLine($"[assembly: AssemblyInformationalVersion(\"{Version}\")]")
 132338                .AppendLine($"namespace {GetNamespace()}")
 132339                .AppendLine("{");
 132340            Classes
 612341                .ForEach((_, v) => codeBuilder.AppendLine(v.GetCode()));
 132342            ScriptDefinitions
 247343                .ForEach((_, v) => codeBuilder.AppendLine(v.GetCode()));
 132344            codeBuilder.AppendLine("}");
 132345            return codeBuilder.ToString();
 132346        }
 347
 348        private string GetNamespace()
 324349        {
 324350            return $"{Name}.V{Version}";
 324351        }
 352
 353        public void SetInstanceValue<T>(string instanceName, string fieldName, T fieldValue)
 12354        {
 12355            SetInstanceValue(instanceName, fieldName, fieldValue, DateTime.UtcNow, QualityLevel.Good);
 12356        }
 357
 358        public void SetInstanceValue<T>(string instanceName, string fieldName, T fieldValue, DateTime timestamp)
 48359        {
 48360            SetInstanceValue(instanceName, fieldName, fieldValue, timestamp, QualityLevel.Good);
 48361        }
 362
 363        public void SetInstanceValue<T>(string instanceName, string fieldName, T fieldValue, QualityLevel qualityLevel)
 0364        {
 0365            SetInstanceValue(instanceName, fieldName, fieldValue, DateTime.UtcNow, qualityLevel);
 0366        }
 367
 368        public void SetInstanceValue<T>(string instanceName, string fieldName, T fieldValue, DateTime timestamp,
 369            QualityLevel qualityLevel)
 60370        {
 60371            Instance instance = GetInstance(instanceName);
 60372            IField field = instance.GetField(fieldName);
 60373            if (field is not Field<T> typedField)
 0374            {
 0375                throw new WrongFieldTypeException(fieldName, fieldValue!.GetType(), field.GetType());
 376            }
 377
 60378            typedField.SetValue(fieldValue, timestamp, qualityLevel);
 60379        }
 380
 381        public List<HistoryRow> GetHistoryValues(string instanceName, HistoryTimeRange query, List<string> fieldNames)
 3382        {
 3383            Instance instance = GetInstance(instanceName);
 3384            InstanceTimeSerieParameters instanceTimeSerieParameters = GetHistoryParametersForFields(instance, fieldNames
 385
 3386            return GetHistoryValues(instanceName, query, instanceTimeSerieParameters);
 2387        }
 388
 389        public List<HistoryRow> GetHistoryValues(string instanceName, HistoryTimeRange query, InstanceTimeSerieParameter
 31390        {
 31391            ValidateTimeRange(query);
 30392            return HistoryStorageEngine!.GetHistoryValues(instanceName, query, instanceTimeSerieParameters, instanceTime
 30393        }
 394
 395        public List<HistoryStatisticRow> GetHistoryStatistics(string instanceName, HistoryStatisticTimeRange query, List
 3396        {
 3397            Instance instance = GetInstance(instanceName);
 7398            InstanceTimeSerieParameters instanceTimeSerieParameters = GetHistoryParametersForFields(instance, [.. fieldN
 3399            List<HistoryStatisticField> statFields = [];
 14400            for (int index = 0; index < fieldNames.Count; index++)
 4401            {
 4402                statFields.Add(new(instanceTimeSerieParameters.Fields[index], fieldNames[index].StatisticFunction));
 4403            }
 3404            return GetHistoryStatistics(instanceName, query, statFields, instanceTimeSerieParameters);
 1405        }
 406
 407        public List<HistoryStatisticRow> GetHistoryStatistics(string instanceName, HistoryStatisticTimeRange query, List
 408             InstanceTimeSerieParameters instanceTimeSerieParameters)
 27409        {
 410            // if query range is a multiple of interval, remove 1 microsecond to end time (To) to avoid returning a new 
 27411            if ((query.To - query.From).Ticks % query.Interval.Ticks == 0)
 14412            {
 14413                query = query with { To = query.To.AddMicroseconds(-1) };
 14414            }
 27415            ValidateTimeRange(query);
 25416            return HistoryStorageEngine!.GetHistoryStatistics(instanceName, query, instanceTimeSerieParameters, fields);
 25417        }
 418
 419        private static void ValidateTimeRange(HistoryTimeRange timeRange)
 58420        {
 58421            if (timeRange.From >= timeRange.To)
 2422            {
 2423                throw new BadHistoryStartTimeException(timeRange.From, timeRange.To);
 424            }
 56425        }
 426
 427        private static void ValidateTimeRange(HistoryStatisticTimeRange timeRange)
 27428        {
 27429            ValidateTimeRange(timeRange as HistoryTimeRange);
 26430            if (timeRange.Interval.Add(TimeSpan.FromMinutes(-1)) > timeRange.To - timeRange.From)
 1431            {
 1432                throw new BadHistoryIntervalException(timeRange.Interval, timeRange.From, timeRange.To);
 433            }
 25434        }
 435
 436        public static InstanceTimeSerieParameters GetHistoryParametersForFields(Instance instance, List<string> fieldNam
 58437        {
 58438            List<IFieldDefinition> fields = [];
 58439            IHistorizationProcessing? historizationProcessing = null;
 58440            List<IHistorizationProcessing>? historizationProcessings = null;
 350441            foreach (string fieldName in fieldNames)
 88442            {
 88443                IFieldDefinition field = instance.Class.GetField(fieldName);
 88444                IHistorizationProcessing? hp = field.ValuePostChangeProcessings
 88445                    .OfType<IHistorizationProcessing>()
 88446                    .FirstOrDefault();
 88447                if (hp != null)
 59448                {
 59449                    historizationProcessing = hp;
 59450                }
 451                else
 29452                {
 29453                    if (historizationProcessing == null)
 29454                    {
 29455                        historizationProcessings = [];
 29456                        instance.Class.FieldDefinitions.Values.ForEach(f =>
 377457                            historizationProcessings.AddRange([.. f.ValuePostChangeProcessings.OfType<IHistorizationProc
 29458                        );
 29459                    }
 58460                    hp = historizationProcessings!.FirstOrDefault(op => op.FieldsToHistorize.Contains(field));
 29461                    if (hp != null && historizationProcessing != null && hp.Name != historizationProcessing.Name)
 0462                    {
 0463                        throw new MixedHistoryProcessingException(field.Name);
 464                    }
 465
 29466                }
 88467                fields.Add(field);
 88468            }
 58469            if (historizationProcessing is null)
 0470            {
 0471                throw new NoHistoryStorageEngineException();
 472            }
 58473            return new InstanceTimeSerieParameters(fields, historizationProcessing);
 58474        }
 475    }
 476}

Methods/Properties

get_ProjectAssemblyLoaderWeakRef()
get_Instances()
.ctor()
.ctor(pva.SuperV.Engine.WipProject)
RegisterScripts()
CreateAndRegisterScript(pva.SuperV.Engine.Processing.ScriptDefinition)
GetId()
SetupProjectAssemblyLoader()
CreateHistoryClassTimeSeries()
CreateHistoryRepositories(pva.SuperV.Engine.HistoryStorage.IHistoryStorageEngine)
CreateInstance(System.String,System.String,System.Boolean)
GetType(System.String)
CreateInstance(System.Type)
CreateScript(System.Type,pva.SuperV.Engine.RunnableProject,pva.SuperV.Engine.Processing.ScriptDefinition)
GetConstructor(System.Type)
GetConstructor(System.Type,System.Type[])
RemoveInstance(System.String)
GetInstance(System.String)
RecreateInstances(pva.SuperV.Engine.WipProject)
Unload()
GetCode()
GetNamespace()
SetInstanceValue(System.String,System.String,T)
SetInstanceValue(System.String,System.String,T,System.DateTime)
SetInstanceValue(System.String,System.String,T,pva.SuperV.Common.QualityLevel)
SetInstanceValue(System.String,System.String,T,System.DateTime,pva.SuperV.Common.QualityLevel)
GetHistoryValues(System.String,pva.SuperV.Engine.HistoryRetrieval.HistoryTimeRange,System.Collections.Generic.List`1<System.String>)
GetHistoryValues(System.String,pva.SuperV.Engine.HistoryRetrieval.HistoryTimeRange,pva.SuperV.Engine.HistoryStorage.InstanceTimeSerieParameters)
GetHistoryStatistics(System.String,pva.SuperV.Engine.HistoryRetrieval.HistoryStatisticTimeRange,System.Collections.Generic.List`1<pva.SuperV.Engine.HistoryRetrieval.HistoryStatisticFieldName>)
GetHistoryStatistics(System.String,pva.SuperV.Engine.HistoryRetrieval.HistoryStatisticTimeRange,System.Collections.Generic.List`1<pva.SuperV.Engine.HistoryRetrieval.HistoryStatisticField>,pva.SuperV.Engine.HistoryStorage.InstanceTimeSerieParameters)
ValidateTimeRange(pva.SuperV.Engine.HistoryRetrieval.HistoryTimeRange)
ValidateTimeRange(pva.SuperV.Engine.HistoryRetrieval.HistoryStatisticTimeRange)
GetHistoryParametersForFields(pva.SuperV.Engine.Instance,System.Collections.Generic.List`1<System.String>)