advanced userinterface design
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// file="NumericalModule"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. All rights reserved.
|
||||
// AuthorGroup: Nitload
|
||||
// Copyright (c) Nitload. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -66,13 +66,15 @@ namespace StgSharp.Mathematics.Numeric
|
||||
internal static SIMDID GlobalIntrinsicMask { get; set; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void InitializeModule()
|
||||
public static void InitializeModule()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
MatrixParallel.Init();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void InitializeModule(IModuleInitializeProfile profile)
|
||||
public void InitializeModule(
|
||||
IModuleInitializeProfile profile
|
||||
)
|
||||
{
|
||||
MatrixParallel.Init();
|
||||
}
|
||||
@@ -80,7 +82,7 @@ namespace StgSharp.Mathematics.Numeric
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void UninitializeModule()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
MatrixParallel.Deinit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// file="MatrixParallel"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. All rights reserved.
|
||||
// AuthorGroup: Nitload
|
||||
// Copyright (c) Nitload. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -29,6 +29,7 @@ using StgSharp.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace StgSharp.Mathematics.Numeric
|
||||
{
|
||||
@@ -45,12 +46,28 @@ namespace StgSharp.Mathematics.Numeric
|
||||
|
||||
public static bool IsParallelSupported { get; } = Environment.ProcessorCount > 6;
|
||||
|
||||
public static bool IsParallelMeaningful => IsParallelSupported && IsInitialized && _meaningful;
|
||||
public static bool IsParallelMeaningful => IsParallelSupported &&
|
||||
IsInitialized &&
|
||||
_meaningful;
|
||||
|
||||
public static int ContextCount { get; private set; }
|
||||
|
||||
private static bool IsInitialized { get; set; }
|
||||
|
||||
internal static void Deinit()
|
||||
{
|
||||
foreach (MatrixParallelThread t in _threads)
|
||||
{
|
||||
t.Retire();
|
||||
t.Dispose();
|
||||
}
|
||||
foreach (MatrixParallelThread t in _retiredThreads) {
|
||||
t.Dispose();
|
||||
}
|
||||
ContextCount = 0;
|
||||
IsInitialized = false;
|
||||
}
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
Dictionary<ulong, int> contextMap = [];
|
||||
@@ -65,8 +82,7 @@ namespace StgSharp.Mathematics.Numeric
|
||||
}
|
||||
contextMap = [];
|
||||
int count = Environment.ProcessorCount;
|
||||
bool hasGlobalSimd = false;
|
||||
SIMDID globalSimd = default;
|
||||
SIMDID globalSimd = new SIMDID(0);
|
||||
_threads = new(count);
|
||||
_retiredThreads = new(RESERVED_THREAD_COUNT);
|
||||
for (int i = 0; i < count; i++)
|
||||
@@ -77,9 +93,10 @@ namespace StgSharp.Mathematics.Numeric
|
||||
|
||||
SIMDID simd = thread.Simd;
|
||||
ulong mask = simd.Mask;
|
||||
contextMap[mask] = contextMap.TryGetValue(mask, out int contextCount) ? (contextCount + 1) : 1;
|
||||
globalSimd = hasGlobalSimd ? UniteSIMDID(globalSimd, simd) : simd;
|
||||
hasGlobalSimd = true;
|
||||
contextMap[mask] = contextMap.TryGetValue(mask, out int contextCount) ?
|
||||
(contextCount + 1) :
|
||||
1;
|
||||
globalSimd = (globalSimd.Mask != 0) ? UniteSIMDID(globalSimd, simd) : simd;
|
||||
}
|
||||
|
||||
IntrinsicContext globalContext = new();
|
||||
@@ -96,7 +113,10 @@ namespace StgSharp.Mathematics.Numeric
|
||||
} else
|
||||
{
|
||||
MatrixParallelThread[] allThreads = _threads.PopRange(_threads.Count);
|
||||
Array.Sort(allThreads, static(left, right) => CompareSIMDID(left.Simd, right.Simd));
|
||||
Array.Sort(allThreads, static(
|
||||
left,
|
||||
right
|
||||
) => CompareSIMDID(left.Simd, right.Simd));
|
||||
int retireCount = Math.Min(RESERVED_THREAD_COUNT, Math.Max(0, allThreads.Length - 1));
|
||||
for (int i = 0; i < retireCount; i++) {
|
||||
RetireThread(allThreads[i]);
|
||||
@@ -111,15 +131,23 @@ namespace StgSharp.Mathematics.Numeric
|
||||
[LibraryImport(Native.LibName, EntryPoint = "sn_compare_simd")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
|
||||
private static partial int CompareSIMDID(SIMDID left, SIMDID right);
|
||||
private static partial int CompareSIMDID(
|
||||
SIMDID left,
|
||||
SIMDID right
|
||||
);
|
||||
|
||||
[LibraryImport(Native.LibName, EntryPoint = "load_intrinsic_function")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
|
||||
private static partial void LoadIntrinsicFunction(IntrinsicContext* context, ulong id);
|
||||
private static partial void LoadIntrinsicFunction(
|
||||
IntrinsicContext* context,
|
||||
ulong id
|
||||
);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void RetireThread(MatrixParallelThread thread)
|
||||
private static void RetireThread(
|
||||
MatrixParallelThread thread
|
||||
)
|
||||
{
|
||||
thread.Retire();
|
||||
_retiredThreads.Push(thread);
|
||||
@@ -128,7 +156,10 @@ namespace StgSharp.Mathematics.Numeric
|
||||
[LibraryImport(Native.LibName, EntryPoint = "sn_get_unite_simd")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
|
||||
private static partial SIMDID UniteSIMDID(SIMDID left, SIMDID right);
|
||||
private static partial SIMDID UniteSIMDID(
|
||||
SIMDID left,
|
||||
SIMDID right
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// file="MatrixParallelTask"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. All rights reserved.
|
||||
// AuthorGroup: Nitload
|
||||
// Copyright (c) Nitload. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -49,7 +49,13 @@ namespace StgSharp.Mathematics.Numeric.Runtime
|
||||
[FieldOffset(0)] public MatrixKernel* Mat1;
|
||||
[FieldOffset(8)] public MatrixKernel* Mat2;
|
||||
[FieldOffset(16)] public MatrixKernel* Mat3;
|
||||
/// <summary>
|
||||
/// Being offset of first kernel in a L4 cache line.
|
||||
/// </summary>
|
||||
[FieldOffset(24)] public int CacheIndex;
|
||||
/// <summary>
|
||||
/// Count of kernels to be calculated in a L4 cache line.
|
||||
/// </summary>
|
||||
[FieldOffset(28)] public int CacheLength;
|
||||
[FieldOffset(32)] public int CommonK;
|
||||
[FieldOffset(36)] public int CommonX;
|
||||
@@ -66,9 +72,13 @@ namespace StgSharp.Mathematics.Numeric.Runtime
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Copy(MatrixParallelTask* source, MatrixParallelTask* target)
|
||||
public static void Copy(
|
||||
MatrixParallelTask* source,
|
||||
MatrixParallelTask* target
|
||||
)
|
||||
{
|
||||
Buffer.MemoryCopy(source, target, sizeof(MatrixParallelTask), sizeof(MatrixParallelTask));
|
||||
Buffer.MemoryCopy(source, target, sizeof(MatrixParallelTask),
|
||||
sizeof(MatrixParallelTask));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// file="MatrixParallelThread"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. All rights reserved.
|
||||
// AuthorGroup: Nitload
|
||||
// Copyright (c) Nitload. All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,6 +31,7 @@ using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.Mathematics.Numeric
|
||||
{
|
||||
@@ -54,7 +55,9 @@ namespace StgSharp.Mathematics.Numeric
|
||||
private readonly ManualResetEventSlim _resetEvent;
|
||||
private Thread _managedThread;
|
||||
|
||||
internal MatrixParallelThread(int poolId)
|
||||
internal MatrixParallelThread(
|
||||
int poolId
|
||||
)
|
||||
{
|
||||
_resetEvent = new ManualResetEventSlim(false);
|
||||
_managedThread = new Thread(MatrixParallelWorkLoad);
|
||||
@@ -119,14 +122,18 @@ namespace StgSharp.Mathematics.Numeric
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void SetSingleThreadTask(ref MatrixParallelTask task)
|
||||
internal void SetSingleThreadTask(
|
||||
ref MatrixParallelTask task
|
||||
)
|
||||
{
|
||||
if (IsSingle) {
|
||||
*_currentTask = task;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
protected virtual void Dispose(
|
||||
bool disposing
|
||||
)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
@@ -183,6 +190,8 @@ namespace StgSharp.Mathematics.Numeric
|
||||
if (IsSingle)
|
||||
{
|
||||
MatrixParallelTask* outer = _currentTask;
|
||||
|
||||
// compute here
|
||||
IsSingle = false;
|
||||
} else
|
||||
{
|
||||
@@ -217,12 +226,17 @@ namespace StgSharp.Mathematics.Numeric
|
||||
[LibraryImport(Native.LibName, EntryPoint = "sn_set_thread_affinity")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
|
||||
internal static partial void SetThreadAffinity(int coreId);
|
||||
internal static partial void SetThreadAffinity(
|
||||
int coreId
|
||||
);
|
||||
|
||||
[LibraryImport(Native.LibName, EntryPoint = "load_intrinsic_function")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
|
||||
private static unsafe partial void LoadIntrinsicFunction(IntrinsicContext* context, ulong id);
|
||||
private static unsafe partial void LoadIntrinsicFunction(
|
||||
IntrinsicContext* context,
|
||||
ulong id
|
||||
);
|
||||
#pragma warning restore CA5393
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/28.2.5 Chrome/138.0.7204.251 Electron/37.6.1 Safari/537.36" version="28.2.5">
|
||||
<diagram name="第 1 页" id="cbTJhW8NyRPDnIMFj9ZQ">
|
||||
<mxGraphModel dx="1425" dy="828" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-36" value="" style="verticalLabelPosition=bottom;verticalAlign=top;html=1;shape=mxgraph.basic.rect;fillColor2=none;strokeWidth=1;size=20;indent=5;fillColor=none;dashed=1;dashPattern=12 12;" vertex="1" parent="1">
|
||||
<mxGeometry x="171" y="355" width="410" height="170" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-37" value="呈现器VP<br>View Presenter" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="251" y="140" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-38" value="试图描述VD<br>View Descriptor" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="251" y="200" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-39" value="用户输入 I" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="391" y="140" width="120" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-40" value="输入抽象 Intent" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="421" y="300" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-41" value="状态快照 Snapshot" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="181" y="300" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-42" value="状态机" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="251" y="380" width="190" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-43" value="仲裁器" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="451" y="380" width="50" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-44" value="并发控制" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="451" y="440" width="50" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-45" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.78;dx=28.86;direction=south;notch=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="391" y="260" width="20" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-46" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.78;dx=28.86;direction=south;notch=0;rotation=-180;" vertex="1" parent="1">
|
||||
<mxGeometry x="341" y="260" width="20" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-47" value="" style="verticalLabelPosition=bottom;verticalAlign=top;html=1;shape=mxgraph.basic.rect;fillColor2=none;strokeWidth=1;size=20;indent=5;fillColor=none;dashed=1;dashPattern=12 12;" vertex="1" parent="1">
|
||||
<mxGeometry x="171" y="110" width="410" height="170" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-48" value="" style="verticalLabelPosition=bottom;verticalAlign=top;html=1;shape=mxgraph.basic.rect;fillColor2=none;strokeWidth=1;size=20;indent=5;fillColor=none;dashed=1;dashPattern=12 12;" vertex="1" parent="1">
|
||||
<mxGeometry x="170" y="580" width="410" height="220" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-49" value="服务实现" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="279" y="650" width="190" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-50" value="命令队列" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="380" y="620" width="89" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-51" value="响应队列" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="279" y="620" width="91" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-52" value="执行结果 Response" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="171" y="540" width="140" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-53" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.78;dx=28.86;direction=south;notch=0;rotation=-180;" vertex="1" parent="1">
|
||||
<mxGeometry x="331" y="500" width="20" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-54" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.78;dx=28.86;direction=south;notch=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="381" y="500" width="20" height="120" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-55" value="后端命令 Command" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="411" y="540" width="120" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-56" value="<font style="font-size: 20px;">视图 View</font>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="591" y="180" width="150" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-57" value="<font style="font-size: 20px;">状态 State</font>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="591" y="425" width="150" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-58" value="<font style="font-size: 20px;">服务 Service</font>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="591" y="695" width="150" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
|
||||
public sealed class PanelStateChangeBindingAttribute(
|
||||
string statePath,
|
||||
string panelHostPath
|
||||
) : Attribute
|
||||
{
|
||||
|
||||
public string StatePath { get; } = string.IsNullOrWhiteSpace(statePath) ?
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(statePath)) :
|
||||
statePath;
|
||||
|
||||
public string PanelHostPath { get; } = string.IsNullOrWhiteSpace(panelHostPath) ?
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(panelHostPath)) :
|
||||
panelHostPath;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class PanelStateNodeAttribute : Attribute { }
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public abstract class ServiceHost
|
||||
{
|
||||
|
||||
protected ServiceHost(
|
||||
string name
|
||||
)
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(name) ?
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)) :
|
||||
name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public abstract Task<object?> ExecuteAsync(
|
||||
ExecuteCommand command,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
public abstract ValueTask InitializeAsync(
|
||||
ServiceLoadCommand command,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
public abstract ValueTask StopAsync(
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public sealed record ServiceLoadAccomplishmentEvent
|
||||
{
|
||||
|
||||
public string? Message { get; init; }
|
||||
|
||||
public ServiceHost? Host { get; init; }
|
||||
|
||||
public bool IsLoaded { get; init; }
|
||||
|
||||
public string ServiceName { get; init; } = string.Empty;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public abstract record ServiceLoadCommand
|
||||
{
|
||||
|
||||
protected ServiceLoadCommand(
|
||||
string serviceName
|
||||
)
|
||||
{
|
||||
ServiceName = string.IsNullOrWhiteSpace(serviceName) ?
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(serviceName)) :
|
||||
serviceName;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<string, object?> Parameters { get; init; } =
|
||||
new Dictionary<string, object?>(StringComparer.Ordinal);
|
||||
|
||||
public string ServiceName { get; init; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public abstract class AppState
|
||||
{
|
||||
|
||||
protected AppState(
|
||||
string name
|
||||
)
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(name) ?
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)) :
|
||||
name;
|
||||
}
|
||||
|
||||
public IDictionary<string, PanelState> Panels { get; } =
|
||||
new Dictionary<string, PanelState>(StringComparer.Ordinal);
|
||||
|
||||
public IDictionary<string, object?> SharedContext { get; } =
|
||||
new Dictionary<string, object?>(StringComparer.Ordinal);
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public abstract class FrontCommandHost
|
||||
{
|
||||
|
||||
public StateHost StateHost { get; private set; } = null!;
|
||||
|
||||
public abstract Task<CommandResult> AcceptAsync(
|
||||
ViewHost source,
|
||||
StateChangeCommand command,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
public abstract Task<CommandResult> AcceptAsync(
|
||||
ViewHost source,
|
||||
ExecuteCommand command,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
internal void Attach(
|
||||
StateHost stateHost
|
||||
)
|
||||
{
|
||||
StateHost = stateHost ?? throw new ArgumentNullException(nameof(stateHost));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public abstract class StateHost
|
||||
{
|
||||
|
||||
protected StateHost(
|
||||
FrontCommandHost frontCommandHost,
|
||||
AppState appState,
|
||||
ServiceManager serviceManager
|
||||
)
|
||||
{
|
||||
FrontCommandHost = frontCommandHost ??
|
||||
throw new ArgumentNullException(nameof(frontCommandHost));
|
||||
AppState = appState ?? throw new ArgumentNullException(nameof(appState));
|
||||
ServiceManager = serviceManager ??
|
||||
throw new ArgumentNullException(nameof(serviceManager));
|
||||
|
||||
FrontCommandHost.Attach(this);
|
||||
ServiceManager.Attach(this);
|
||||
}
|
||||
|
||||
public AppState AppState { get; }
|
||||
|
||||
public FrontCommandHost FrontCommandHost { get; }
|
||||
|
||||
public ServiceManager ServiceManager { get; }
|
||||
|
||||
public virtual async Task<CommandResult> PostAsync(
|
||||
ViewHost source,
|
||||
StateChangeCommand command,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
|
||||
CommandResult result = await FrontCommandHost.AcceptAsync(source, command, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await source.ReceiveCommandResultAsync(result, cancellationToken).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual async Task<CommandResult> PostAsync(
|
||||
ViewHost source,
|
||||
ExecuteCommand command,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
|
||||
CommandResult result = await FrontCommandHost.AcceptAsync(source, command, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await source.ReceiveCommandResultAsync(result, cancellationToken).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public abstract class PanelState
|
||||
{
|
||||
|
||||
protected PanelState(
|
||||
string name
|
||||
)
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(name) ?
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)) :
|
||||
name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public abstract class ServiceManager
|
||||
{
|
||||
|
||||
public StateHost StateHost { get; private set; } = null!;
|
||||
|
||||
public abstract Task<object?> ExecuteAsync(
|
||||
ExecuteCommand command,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
public abstract Task<ServiceLoadAccomplishmentEvent> LoadAsync(
|
||||
ServiceLoadCommand command,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
public abstract ValueTask RegisterAsync(
|
||||
string serviceName,
|
||||
ServiceHost serviceHost,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
internal void Attach(
|
||||
StateHost stateHost
|
||||
)
|
||||
{
|
||||
StateHost = stateHost ?? throw new ArgumentNullException(nameof(stateHost));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public sealed record CommandResult
|
||||
{
|
||||
|
||||
public string? Message { get; init; }
|
||||
|
||||
public object? Data { get; init; }
|
||||
|
||||
public bool IsSuccess { get; init; }
|
||||
|
||||
public bool IsRejected { get; init; }
|
||||
|
||||
public bool IsAborted { get; init; }
|
||||
|
||||
public Guid CommandId { get; init; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public abstract record ExecuteCommand
|
||||
{
|
||||
|
||||
protected ExecuteCommand(
|
||||
string operationName
|
||||
)
|
||||
{
|
||||
OperationName = string.IsNullOrWhiteSpace(operationName) ?
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(operationName)) :
|
||||
operationName;
|
||||
}
|
||||
|
||||
public Guid CommandId { get; init; } = Guid.NewGuid();
|
||||
|
||||
public IReadOnlyList<object?> Parameters { get; init; } = Array.Empty<object?>();
|
||||
|
||||
public string OperationName { get; init; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public abstract class ViewHost
|
||||
{
|
||||
|
||||
protected ViewHost(
|
||||
string name,
|
||||
StateHost stateHost
|
||||
)
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(name) ?
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)) :
|
||||
name;
|
||||
StateHost = stateHost ?? throw new ArgumentNullException(nameof(stateHost));
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
protected StateHost StateHost { get; }
|
||||
|
||||
public virtual Task<CommandResult> PostAsync(
|
||||
StateChangeCommand command,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
return StateHost.PostAsync(this, command, cancellationToken);
|
||||
}
|
||||
|
||||
public virtual Task<CommandResult> PostAsync(
|
||||
ExecuteCommand command,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
return StateHost.PostAsync(this, command, cancellationToken);
|
||||
}
|
||||
|
||||
public virtual ValueTask ReceiveCommandResultAsync(
|
||||
CommandResult result,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(result);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public abstract record StateChangeCommand
|
||||
{
|
||||
|
||||
protected StateChangeCommand(
|
||||
string statePath
|
||||
)
|
||||
{
|
||||
StatePath = string.IsNullOrWhiteSpace(statePath) ?
|
||||
throw new ArgumentException("Value cannot be null or whitespace.", nameof(statePath)) :
|
||||
statePath;
|
||||
}
|
||||
|
||||
public object? Value { get; init; }
|
||||
|
||||
public Guid CommandId { get; init; } = Guid.NewGuid();
|
||||
|
||||
public string StatePath { get; init; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace StgSharp.UserInterface
|
||||
{
|
||||
public sealed record StateValueCommand<TValue> : StateChangeCommand
|
||||
{
|
||||
|
||||
public StateValueCommand(
|
||||
string statePath,
|
||||
TValue value
|
||||
) : base(statePath)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public new TValue Value { get; init; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
# 增量生成器框架说明
|
||||
|
||||
本文档说明当前 Generator 文件夹里这套增量生成器骨架为什么这样拆,以及每一层现在承担什么职责。
|
||||
|
||||
这里讲的是“结构设计”,不是“最终 VSS 业务代码怎么生成”。
|
||||
|
||||
---
|
||||
|
||||
## 1. 当前结论
|
||||
|
||||
当前这套结构不再是“一个字段触发一个小生成器”,而是:
|
||||
|
||||
1. 一个统一入口生成器
|
||||
2. 一个类型级候选识别管线
|
||||
3. 一个共享中间模型
|
||||
4. 三个内部分析模块
|
||||
5. 一个统一发射器
|
||||
6. 一个诊断层预留点
|
||||
|
||||
也就是说,设计目标已经从“节点枚举小功能”升级成“围绕一个状态类型进行多维分析”。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前文件与职责
|
||||
|
||||
当前 Generator 文件夹中的核心文件是:
|
||||
|
||||
1. `PanelStateIncrementalGenerator.cs`
|
||||
2. `PanelStateIncrementalGenerator.Pipeline.cs`
|
||||
3. `PanelStateIncrementalGenerator.Models.cs`
|
||||
4. `PanelStateIncrementalGenerator.Analysis.cs`
|
||||
5. `PanelStateIncrementalGenerator.StateSymbols.cs`
|
||||
6. `PanelStateIncrementalGenerator.StateChangeBindings.cs`
|
||||
7. `PanelStateIncrementalGenerator.StateCommandHandlers.cs`
|
||||
8. `PanelStateIncrementalGenerator.Emitter.cs`
|
||||
9. `PanelStateIncrementalGenerator.Diagnostics.cs`
|
||||
|
||||
它们的职责可以概括成下面这张表:
|
||||
|
||||
| 文件 | 角色 | 负责什么 |
|
||||
| --- | --- | --- |
|
||||
| `PanelStateIncrementalGenerator.cs` | 入口层 | 把 Roslyn 的增量管线接起来 |
|
||||
| `PanelStateIncrementalGenerator.Pipeline.cs` | 候选管线层 | 识别类型级候选,并收集已知标记成员 |
|
||||
| `PanelStateIncrementalGenerator.Models.cs` | 中间模型层 | 定义共享输入、目标、分析结果模型 |
|
||||
| `PanelStateIncrementalGenerator.Analysis.cs` | 协调层 | 驱动三个内部分析模块并汇总输出 |
|
||||
| `PanelStateIncrementalGenerator.StateSymbols.cs` | 分析模块 1 | 分析状态符号相关成员 |
|
||||
| `PanelStateIncrementalGenerator.StateChangeBindings.cs` | 分析模块 2 | 分析状态变化绑定相关成员 |
|
||||
| `PanelStateIncrementalGenerator.StateCommandHandlers.cs` | 分析模块 3 | 基于前两部分结果派生状态变化命令处理结果 |
|
||||
| `PanelStateIncrementalGenerator.Emitter.cs` | 发射层 | 把分析结果输出成 `.g.cs` |
|
||||
| `PanelStateIncrementalGenerator.Diagnostics.cs` | 诊断层 | 预留统一诊断定义位置 |
|
||||
|
||||
一句话总结现在的结构:
|
||||
|
||||
> 一个入口识别类型声明,进入统一模型,再在内部拆成三个并列分析部分。
|
||||
|
||||
---
|
||||
|
||||
## 3. 入口层
|
||||
|
||||
对应文件:
|
||||
|
||||
`PanelStateIncrementalGenerator.cs`
|
||||
|
||||
它的职责仍然很单纯:
|
||||
|
||||
1. 建立 SyntaxProvider
|
||||
2. 收集类型级候选
|
||||
3. 与 Compilation 合并
|
||||
4. 调用统一分析器
|
||||
5. 把结果交给发射器
|
||||
|
||||
它不应该承担这些事情:
|
||||
|
||||
1. 不应该写复杂业务规则
|
||||
2. 不应该直接拼大段生成代码
|
||||
3. 不应该依赖某一块具体的状态逻辑
|
||||
|
||||
入口层越薄,后面三部分演化时越安全。
|
||||
|
||||
---
|
||||
|
||||
## 4. Pipeline 层
|
||||
|
||||
对应文件:
|
||||
|
||||
`PanelStateIncrementalGenerator.Pipeline.cs`
|
||||
|
||||
这层的重点已经从“字段筛选”切换成“类型筛选”。
|
||||
|
||||
### 4.1 `IsCandidate`
|
||||
|
||||
这一步只做便宜的语法级判断。
|
||||
|
||||
当前策略是:
|
||||
|
||||
1. 只看 `TypeDeclarationSyntax`
|
||||
2. 该类型内部至少存在一个带 Attribute 的字段、属性或方法
|
||||
|
||||
这一步允许误报,但必须便宜。
|
||||
|
||||
### 4.2 `Transform`
|
||||
|
||||
这一步开始做语义级确认。
|
||||
|
||||
当前逻辑是:
|
||||
|
||||
1. 拿到类型符号 `INamedTypeSymbol`
|
||||
2. 遍历该类型成员
|
||||
3. 识别已知 Attribute
|
||||
4. 收集所有命中的成员
|
||||
5. 构造一个类型级目标 `PanelStateIncrementalGeneratorTypeTarget`
|
||||
|
||||
这一步的核心意义是:
|
||||
|
||||
1. 根输入单位是“类型”
|
||||
2. 成员只是这个类型上的分析素材
|
||||
3. 后面的三部分都共享同一份素材,而不是各自重新扫一遍类型
|
||||
|
||||
---
|
||||
|
||||
## 5. Models 层
|
||||
|
||||
对应文件:
|
||||
|
||||
`PanelStateIncrementalGenerator.Models.cs`
|
||||
|
||||
中间模型的作用,是把 Roslyn 的语义对象整理成稳定的数据结构,供后面多个模块共享。
|
||||
|
||||
当前关键模型包括:
|
||||
|
||||
1. `PanelStateIncrementalGeneratorMarkedMember`
|
||||
2. `PanelStateIncrementalGeneratorTypeTarget`
|
||||
3. `PanelStateIncrementalGeneratorInput`
|
||||
4. `PanelStateIncrementalGeneratorTypeAnalysis`
|
||||
5. `PanelStateIncrementalGeneratorOutput`
|
||||
|
||||
以及三个内部分析结果:
|
||||
|
||||
1. `PanelStateSymbolPartAnalysis`
|
||||
2. `StateChangeBindingPartAnalysis`
|
||||
3. `StateChangeCommandHandlerPartAnalysis`
|
||||
|
||||
其中第三个结果不再对应一个额外的用户声明 attribute,而是前两部分分析完成后自动推导出来的调度结果。
|
||||
|
||||
这意味着现在共享的不是某个模块实例里的私有字段,而是显式模型。
|
||||
|
||||
这点很重要,因为源生成器里真正稳定的共享方式就是:
|
||||
|
||||
1. 共享模型
|
||||
2. 共享编译上下文
|
||||
3. 共享归一化后的分析结果
|
||||
|
||||
而不是模块之间互相暴露运行时对象。
|
||||
|
||||
---
|
||||
|
||||
## 6. Analysis 层
|
||||
|
||||
对应文件:
|
||||
|
||||
1. `PanelStateIncrementalGenerator.Analysis.cs`
|
||||
2. `PanelStateIncrementalGenerator.StateSymbols.cs`
|
||||
3. `PanelStateIncrementalGenerator.StateChangeBindings.cs`
|
||||
4. `PanelStateIncrementalGenerator.StateCommandHandlers.cs`
|
||||
|
||||
这一层是当前结构里最关键的变化。
|
||||
|
||||
以前如果只做一个节点枚举生成器,那么 Pipeline 之后可以直接进 Emitter。
|
||||
|
||||
但现在不行,因为你已经明确有三块并列需求:
|
||||
|
||||
1. StateSymbols
|
||||
2. StateChangeBindings
|
||||
3. StateChangeCommandHandlers
|
||||
|
||||
所以中间必须有一个统一分析层。
|
||||
|
||||
它负责:
|
||||
|
||||
1. 对每个类型目标进行统一调度
|
||||
2. 先调用前两个显式分析模块
|
||||
3. 再基于前两部分结果运行第三个派生调度阶段
|
||||
4. 把三个分析结果装配成一个类型级总分析结果
|
||||
5. 保证最终发射器看到的是完整上下文
|
||||
|
||||
这就是“一入口,三部分”的真正落点。
|
||||
|
||||
---
|
||||
|
||||
## 7. Emitter 层
|
||||
|
||||
对应文件:
|
||||
|
||||
`PanelStateIncrementalGenerator.Emitter.cs`
|
||||
|
||||
发射器当前还是一个骨架,但它已经不再只是输出“发现了哪些字段”,而是输出:
|
||||
|
||||
1. 发现了哪些类型
|
||||
2. 每个类型在三部分里各命中了多少成员
|
||||
|
||||
这类 manifest 的作用不是业务交付,而是结构校验:
|
||||
|
||||
1. 证明统一入口已经接通
|
||||
2. 证明类型级分析结果已经被汇总
|
||||
3. 证明发射器能看到三部分的完整结果
|
||||
|
||||
等后面要真正生成 enum、binding 路由、handler 路由时,这个文件会自然长大。
|
||||
|
||||
---
|
||||
|
||||
## 8. Diagnostics 层
|
||||
|
||||
对应文件:
|
||||
|
||||
`PanelStateIncrementalGenerator.Diagnostics.cs`
|
||||
|
||||
当前它依然很薄,这是正常的。
|
||||
|
||||
它的存在价值是为后续统一诊断留一个稳定出口,例如:
|
||||
|
||||
1. 标记位置不合法
|
||||
2. 同一类型内声明冲突
|
||||
3. 某个 handler 签名不符合规则
|
||||
4. 某个 binding 指向不存在的状态路径
|
||||
5. 同一路由被重复生成
|
||||
|
||||
真正重要的不是现在有没有很多诊断,而是不要等逻辑长大后再到处散着写诊断定义。
|
||||
|
||||
---
|
||||
|
||||
## 9. 为什么必须是“一入口三部分”
|
||||
|
||||
如果把这三块拆成三个彼此独立的生成器,会立刻遇到两个问题:
|
||||
|
||||
1. 元数据重复扫描
|
||||
2. 诊断信息无法自然共享
|
||||
|
||||
更严重的是,多个生成器之间并不适合靠实例成员互相通信。
|
||||
|
||||
因此更合理的结构是:
|
||||
|
||||
1. 一个根入口
|
||||
2. 一个共享模型
|
||||
3. 三个内部分析部分
|
||||
4. 一个统一发射出口
|
||||
|
||||
这就是现在这套结构背后的核心原则。
|
||||
|
||||
---
|
||||
|
||||
## 10. 当前阶段的定位
|
||||
|
||||
现在这套代码还只是“结构骨架”,不是“业务生成完成版”。
|
||||
|
||||
它已经完成的事情是:
|
||||
|
||||
1. 根入口已经统一
|
||||
2. 输入粒度已经切到类型级
|
||||
3. 三部分内部分析已经独立成模块
|
||||
4. 发射器已经能看到完整汇总结果
|
||||
|
||||
还没有完成的事情是:
|
||||
|
||||
1. 真实的状态符号生成
|
||||
2. 真实的状态变化绑定生成
|
||||
3. 真实的基于前两部分推导出的状态命令路由生成
|
||||
4. 真实的统一诊断规则
|
||||
|
||||
也就是说,当前目标不是“把功能做完”,而是先把以后不会推翻的结构搭稳。
|
||||
|
||||
但从工程实现看,它其实是几件不同的事情:
|
||||
|
||||
1. Roslyn 入口接线
|
||||
2. 低成本筛候选
|
||||
3. 高成本做语义确认
|
||||
4. 把结果整理成稳定模型
|
||||
5. 输出代码
|
||||
6. 在不合法时报诊断
|
||||
|
||||
如果这几件事全写在一个文件里,短期看更短,长期一定更难改。
|
||||
|
||||
所以你可以把这套拆分理解成:
|
||||
|
||||
> 它不是为了“显得标准”,而是为了让后面真正加业务逻辑时,不需要每次都从一坨 Roslyn 接线代码里翻东西。
|
||||
|
||||
---
|
||||
|
||||
## 9. 一个更直观的心智模型
|
||||
|
||||
你可以把当前框架理解成一条流水线:
|
||||
|
||||
1. 入口层:把流水线接起来
|
||||
2. Pipeline:把矿石挑出来
|
||||
3. Models:把矿石做成标准坯料
|
||||
4. Emitter:把坯料加工成成品代码
|
||||
5. Diagnostics:发现废品时给出报错
|
||||
|
||||
如果写成一句更短的话:
|
||||
|
||||
1. 找目标
|
||||
2. 抽模型
|
||||
3. 发代码
|
||||
4. 报问题
|
||||
|
||||
---
|
||||
|
||||
## 10. 你后面最可能怎么继续扩展
|
||||
|
||||
如果按你现在的 VSS 方向继续,这套框架下一步通常会往下面长:
|
||||
|
||||
### 第一步:把 Target 变得更像“节点描述”
|
||||
|
||||
例如不只是保存字段名,还保存:
|
||||
|
||||
1. 节点名
|
||||
2. 所属 PanelState
|
||||
3. 路径前缀
|
||||
4. 可生成成员集合
|
||||
|
||||
### 第二步:在 Emitter 中真正生成 enum
|
||||
|
||||
例如:
|
||||
|
||||
1. 每个 PanelState 对应一个 `xxxNode` enum
|
||||
2. enum 成员由被标记字段自动生成
|
||||
|
||||
### 第三步:继续生成路由代码
|
||||
|
||||
例如:
|
||||
|
||||
1. string path -> enum
|
||||
2. enum -> setter/handler
|
||||
|
||||
### 第四步:再接入受控状态写入与分布式 observer hook
|
||||
|
||||
例如:
|
||||
|
||||
1. pre-change handler 调用点
|
||||
2. set property
|
||||
3. 由源生成器织入的轻量 post-change observer hook
|
||||
4. 局部状态广播或内部同步
|
||||
|
||||
这里的 observer hook 只负责很薄的一层内部广播,不负责 view 重绘或业务执行。
|
||||
在 VSS 中,这两部分分别由 command/result 同步链路与 service 逻辑承担。
|
||||
|
||||
---
|
||||
|
||||
## 11. 对你当前项目的最简理解
|
||||
|
||||
如果你觉得这套骨架有点“过工程化”,可以先只记住下面这五句话:
|
||||
|
||||
1. `IncrementalGenerator` 文件只是总入口,不负责细活。
|
||||
2. `Pipeline` 文件负责找出哪些代码需要生成。
|
||||
3. `Models` 文件负责把识别结果整理成稳定的数据结构。
|
||||
4. `Emitter` 文件负责把这些数据真正输出成 `.g.cs`。
|
||||
5. `Diagnostics` 文件负责以后统一放错误和警告定义。
|
||||
|
||||
你后面要改业务逻辑,主要会集中在 `Pipeline`、`Models`、`Emitter` 这三层。
|
||||
|
||||
---
|
||||
|
||||
## 12. 最后一句话
|
||||
|
||||
当前这套框架不是“生成器已经很复杂了”,而是“先把复杂度分栏放好”。
|
||||
|
||||
你后面真正写逻辑时,应该优先记住这个原则:
|
||||
|
||||
1. Pipeline 决定“找谁”
|
||||
2. Models 决定“怎么表示它”
|
||||
3. Emitter 决定“怎么生成它”
|
||||
|
||||
只要这三个边界不乱,后面的业务逻辑就算加很多,也还会比较稳。
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace StgSharp.UserInterface.Generator;
|
||||
|
||||
internal static class PanelStateIncrementalGeneratorAnalysis
|
||||
{
|
||||
public static PanelStateIncrementalGeneratorOutput Analyze(
|
||||
PanelStateIncrementalGeneratorInput input
|
||||
)
|
||||
{
|
||||
if (input.TypeTargets.IsDefaultOrEmpty)
|
||||
{
|
||||
return new PanelStateIncrementalGeneratorOutput(
|
||||
input.Compilation,
|
||||
ImmutableArray<PanelStateIncrementalGeneratorTypeAnalysis>.Empty);
|
||||
}
|
||||
|
||||
HashSet<string> emittedTypes = new(StringComparer.Ordinal);
|
||||
ImmutableArray<PanelStateIncrementalGeneratorTypeAnalysis>.Builder builder =
|
||||
ImmutableArray.CreateBuilder<PanelStateIncrementalGeneratorTypeAnalysis>();
|
||||
|
||||
foreach (PanelStateIncrementalGeneratorTypeTarget target in input.TypeTargets
|
||||
.OrderBy(static item => item.TypeDisplayName, StringComparer.Ordinal))
|
||||
{
|
||||
if (!emittedTypes.Add(target.TypeDisplayName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PanelStateSymbolPartAnalysis stateSymbols = PanelStateIncrementalGeneratorStateSymbols.Analyze(target);
|
||||
StateChangeBindingPartAnalysis stateChangeBindings = PanelStateIncrementalGeneratorStateChangeBindings.Analyze(target);
|
||||
|
||||
builder.Add(
|
||||
new PanelStateIncrementalGeneratorTypeAnalysis(
|
||||
target,
|
||||
stateSymbols,
|
||||
stateChangeBindings,
|
||||
PanelStateIncrementalGeneratorStateCommandHandlers.Analyze(
|
||||
target,
|
||||
stateSymbols,
|
||||
stateChangeBindings)));
|
||||
}
|
||||
|
||||
return new PanelStateIncrementalGeneratorOutput(
|
||||
input.Compilation,
|
||||
builder.ToImmutable());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
# PanelStateIncrementalGenerator 结构草案
|
||||
|
||||
本文档说明当前 `PanelStateIncrementalGenerator` 的设计目标:
|
||||
|
||||
1. 只有一个增量生成器入口点
|
||||
2. 入口识别的是“类型声明”,不是单个字段
|
||||
3. 类型进入统一上下文后,内部拆成三个独立分析部分
|
||||
4. 三部分不互相依赖实例成员,只共享同一份中间模型
|
||||
|
||||
---
|
||||
|
||||
## 1. 为什么入口要识别类型声明
|
||||
|
||||
VSS 后续的生成需求不再只是“某个字段生成 enum”,而是围绕某个状态类型展开:
|
||||
|
||||
1. 该类型有哪些公开状态符号
|
||||
2. 该类型有哪些状态变化绑定
|
||||
3. 基于前两者可以推导出哪些 command handler 路由入口
|
||||
|
||||
因此,最合适的根单位不是单个字段,而是一个类型声明。
|
||||
|
||||
也就是说,入口阶段做的事情应该是:
|
||||
|
||||
1. 找出值得参与 VSS 生成的类型
|
||||
2. 收集该类型上所有已知标记成员
|
||||
3. 把这份类型级目标交给前两部分分析,再进入第三部分派生调度
|
||||
|
||||
---
|
||||
|
||||
## 2. 三部分内部分析
|
||||
|
||||
当前骨架把内部分析拆成三块:
|
||||
|
||||
1. `StateSymbols`
|
||||
2. `StateChangeBindings`
|
||||
3. `StateChangeCommandHandlers`
|
||||
|
||||
### 2.1 StateSymbols
|
||||
|
||||
这一部分负责状态符号层。
|
||||
|
||||
目标是为后续生成这些东西打基础:
|
||||
|
||||
1. 状态节点枚举
|
||||
2. 状态项元数据
|
||||
3. state 向量上的属性节点
|
||||
4. 节点到属性的静态映射
|
||||
|
||||
### 2.2 StateChangeBindings
|
||||
|
||||
这一部分负责绑定层。
|
||||
|
||||
目标是分析:
|
||||
|
||||
1. 哪些状态变化需要绑定到 handler
|
||||
2. 哪些声明使用了 `StateChangeBinding` 之类的标记
|
||||
3. 后续需要生成怎样的静态接线代码
|
||||
|
||||
### 2.3 StateChangeCommandHandlers
|
||||
|
||||
这一部分负责调度层。
|
||||
|
||||
它不是一个额外的 attribute 输入层,而是前两部分完成后自动运行的派生阶段。
|
||||
|
||||
目标是分析:
|
||||
|
||||
1. 哪些状态符号和 binding 结果可以组成 command handler 路由入口
|
||||
2. 后续如何生成状态变化命令到 handler 的静态路由
|
||||
3. 如何在不依赖运行时字符串解释器的前提下构建调度表
|
||||
|
||||
---
|
||||
|
||||
## 3. 为什么三部分不能互相暴露实例成员
|
||||
|
||||
原因很简单:
|
||||
|
||||
1. Roslyn 生成器不是运行时 IOC 容器
|
||||
2. 不应该设计成多个生成器实例互相通信
|
||||
3. 也不应该让一个分析模块依赖另一个模块的内部字段状态
|
||||
|
||||
正确方式是:
|
||||
|
||||
1. 统一入口构建共享中间模型
|
||||
2. 三部分都基于这份中间模型分析
|
||||
3. 最终统一做诊断和发射
|
||||
|
||||
也就是说,共享的应该是:
|
||||
|
||||
1. 类型级目标
|
||||
2. 已知标记成员
|
||||
3. 编译上下文
|
||||
4. 归一化后的分析结果
|
||||
|
||||
而不是:
|
||||
|
||||
1. 某个模块里的字典字段
|
||||
2. 某个模块先运行出来的实例状态
|
||||
3. 依赖执行顺序的临时对象
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前骨架中的文件分工
|
||||
|
||||
当前相关文件如下:
|
||||
|
||||
1. `PanelStateIncrementalGenerator.cs`
|
||||
2. `PanelStateIncrementalGenerator.Pipeline.cs`
|
||||
3. `PanelStateIncrementalGenerator.Models.cs`
|
||||
4. `PanelStateIncrementalGenerator.Analysis.cs`
|
||||
5. `PanelStateIncrementalGenerator.StateSymbols.cs`
|
||||
6. `PanelStateIncrementalGenerator.StateChangeBindings.cs`
|
||||
7. `PanelStateIncrementalGenerator.StateCommandHandlers.cs`
|
||||
8. `PanelStateIncrementalGenerator.Emitter.cs`
|
||||
9. `PanelStateIncrementalGenerator.Diagnostics.cs`
|
||||
|
||||
它们各自的职责是:
|
||||
|
||||
1. `PanelStateIncrementalGenerator.cs`
|
||||
负责总入口接线
|
||||
|
||||
2. `PanelStateIncrementalGenerator.Pipeline.cs`
|
||||
负责从语法树中找出类型级候选,并收集已知标记成员
|
||||
|
||||
3. `PanelStateIncrementalGenerator.Models.cs`
|
||||
负责共享中间模型和三部分分析结果模型
|
||||
|
||||
4. `PanelStateIncrementalGenerator.Analysis.cs`
|
||||
负责协调三部分分析模块,并汇总出统一输出上下文
|
||||
|
||||
5. `PanelStateIncrementalGenerator.StateSymbols.cs`
|
||||
负责状态符号层分析
|
||||
|
||||
6. `PanelStateIncrementalGenerator.StateChangeBindings.cs`
|
||||
负责状态变化绑定层分析
|
||||
|
||||
7. `PanelStateIncrementalGenerator.StateCommandHandlers.cs`
|
||||
负责基于前两部分结果派生状态变化命令处理层
|
||||
|
||||
8. `PanelStateIncrementalGenerator.Emitter.cs`
|
||||
负责真正输出当前轮次的生成代码
|
||||
|
||||
9. `PanelStateIncrementalGenerator.Diagnostics.cs`
|
||||
负责后续统一挂诊断定义
|
||||
|
||||
---
|
||||
|
||||
## 5. 当前状态
|
||||
|
||||
现在这套代码还只是骨架,业务逻辑没有真正接进去。
|
||||
|
||||
当前它已经具备的结构能力是:
|
||||
|
||||
1. 入口已经从字段级切到类型级
|
||||
2. 已知标记成员会在 Pipeline 阶段被收集
|
||||
3. 内部已经形成“前两部分显式分析 + 第三部分派生调度”的结构
|
||||
4. 发射器已经能看到每个类型在三部分里各自命中了多少成员
|
||||
|
||||
也就是说,现在已经完成的是“结构立住”,还没有进入“具体生成 VSS 代码”的阶段。
|
||||
|
||||
---
|
||||
|
||||
## 6. 后续最自然的实现顺序
|
||||
|
||||
如果继续往下做,建议顺序是:
|
||||
|
||||
1. 先完善 `StateSymbols`
|
||||
2. 再完善 `StateChangeBindings`
|
||||
3. 最后完善 `StateChangeCommandHandlers`
|
||||
|
||||
原因是:
|
||||
|
||||
1. 状态符号层是基础设施
|
||||
2. 绑定层建立在状态符号层之上
|
||||
3. command handler 调度层由前两层自动派生
|
||||
|
||||
所以三部分虽然是并列模块,但实现优先级并不是并列的。
|
||||
|
||||
---
|
||||
|
||||
## 7. 一句话总结
|
||||
|
||||
当前设计不是“三个生成器共享上下文”,而是:
|
||||
|
||||
一个 `PanelStateIncrementalGenerator` 统一入口,识别类型声明,先完成前两部分分析,再自动进入第三部分派生调度。
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace StgSharp.UserInterface.Generator;
|
||||
|
||||
internal static class PanelStateIncrementalGeneratorDiagnostics
|
||||
{
|
||||
public const string Category = "StgSharp.UserInterface.Generator";
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace StgSharp.UserInterface.Generator
|
||||
{
|
||||
internal static class PanelStateIncrementalGeneratorEmitter
|
||||
{
|
||||
|
||||
public static void Emit(
|
||||
SourceProductionContext context,
|
||||
PanelStateIncrementalGeneratorOutput source
|
||||
)
|
||||
{
|
||||
if (source.TypeAnalyses.IsDefaultOrEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder builder = new();
|
||||
_ = builder.AppendLine("// <auto-generated />");
|
||||
_ = builder.AppendLine("#nullable enable");
|
||||
_ = builder.AppendLine();
|
||||
_ = builder.AppendLine("namespace StgSharp.UserInterface.Generator");
|
||||
_ = builder.AppendLine("{");
|
||||
_ = builder.AppendLine(" internal static class PanelStateIncrementalGeneratorManifest");
|
||||
_ = builder.AppendLine(" {");
|
||||
_ = builder.AppendLine(" /*");
|
||||
_ = builder.AppendLine(" internal static string[] DiscoveredTypes { get; } = new string[]");
|
||||
_ = builder.AppendLine(" {");
|
||||
|
||||
foreach (string typeName in source.TypeAnalyses
|
||||
.Select(static analysis => analysis.Target.TypeDisplayName)
|
||||
.Distinct()
|
||||
.OrderBy(static name => name, StringComparer.Ordinal))
|
||||
{
|
||||
_ = builder.Append(" \"");
|
||||
_ = builder.Append(typeName.Replace("\"", "\"\"", StringComparison.Ordinal));
|
||||
_ = builder.AppendLine("\",");
|
||||
}
|
||||
|
||||
_ = builder.AppendLine(" };");
|
||||
_ = builder.AppendLine();
|
||||
_ = builder.AppendLine(" internal static string[] AnalysisSummary { get; } = new string[]");
|
||||
_ = builder.AppendLine(" {");
|
||||
|
||||
foreach (PanelStateIncrementalGeneratorTypeAnalysis analysis in source.TypeAnalyses
|
||||
.OrderBy(static item => item.Target.TypeDisplayName, StringComparer.Ordinal))
|
||||
{
|
||||
string summary = $"{analysis.Target.TypeDisplayName} | symbols={analysis.StateSymbols.Members.Length} | bindings={analysis.StateChangeBindings.Members.Length} | handlers={analysis.StateChangeCommandHandlers.Members.Length}";
|
||||
_ = builder.Append(" \"");
|
||||
_ = builder.Append(summary.Replace("\"", "\"\"", StringComparison.Ordinal));
|
||||
_ = builder.AppendLine("\",");
|
||||
}
|
||||
|
||||
_ = builder.AppendLine(" };");
|
||||
_ = builder.AppendLine(" }");
|
||||
_ = builder.AppendLine("}");
|
||||
|
||||
context.AddSource(
|
||||
"PanelStateIncrementalGeneratorManifest.g.cs",
|
||||
SourceText.From(builder.ToString(), Encoding.UTF8));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StgSharp.UserInterface.Generator
|
||||
{
|
||||
internal static class PanelStateIncrementalGeneratorKnownAttributes
|
||||
{
|
||||
|
||||
public const string PanelStateNodeAttribute = "StgSharp.UserInterface.PanelStateNodeAttribute";
|
||||
|
||||
public const string StateChangeBindingAttribute = "StgSharp.UserInterface.StateChangeBindingAttribute";
|
||||
|
||||
public static bool IsKnown(
|
||||
string? metadataName
|
||||
)
|
||||
{
|
||||
return IsStateSymbolAttribute(metadataName) || metadataName == StateChangeBindingAttribute;
|
||||
}
|
||||
|
||||
public static bool IsStateSymbolAttribute(
|
||||
string? metadataName
|
||||
)
|
||||
{
|
||||
return metadataName == PanelStateNodeAttribute;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed record PanelStateIncrementalGeneratorMarkedMember(
|
||||
ISymbol Symbol,
|
||||
string AttributeMetadataName,
|
||||
string SymbolName,
|
||||
string SymbolTypeName
|
||||
);
|
||||
|
||||
internal sealed record PanelStateIncrementalGeneratorTypeTarget(
|
||||
INamedTypeSymbol Type,
|
||||
string TypeDisplayName,
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> MarkedMembers
|
||||
);
|
||||
|
||||
internal readonly record struct PanelStateIncrementalGeneratorInput(
|
||||
Compilation Compilation,
|
||||
ImmutableArray<PanelStateIncrementalGeneratorTypeTarget> TypeTargets
|
||||
);
|
||||
|
||||
internal sealed record PanelStateIncrementalGeneratorTypeAnalysis(
|
||||
PanelStateIncrementalGeneratorTypeTarget Target,
|
||||
PanelStateSymbolPartAnalysis StateSymbols,
|
||||
StateChangeBindingPartAnalysis StateChangeBindings,
|
||||
StateChangeCommandHandlerPartAnalysis StateChangeCommandHandlers
|
||||
);
|
||||
|
||||
internal readonly record struct PanelStateIncrementalGeneratorOutput(
|
||||
Compilation Compilation,
|
||||
ImmutableArray<PanelStateIncrementalGeneratorTypeAnalysis> TypeAnalyses
|
||||
);
|
||||
|
||||
internal sealed record PanelStateSymbolPartAnalysis(
|
||||
string OwnerTypeName,
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> Members
|
||||
);
|
||||
|
||||
internal sealed record StateChangeBindingPartAnalysis(
|
||||
string OwnerTypeName,
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> Members
|
||||
);
|
||||
|
||||
internal sealed record StateChangeCommandHandlerPartAnalysis(
|
||||
string OwnerTypeName,
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> Members
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using System.Threading;
|
||||
|
||||
namespace StgSharp.UserInterface.Generator
|
||||
{
|
||||
internal static class PanelStateIncrementalGeneratorPipeline
|
||||
{
|
||||
|
||||
public static bool IsCandidate(
|
||||
SyntaxNode node
|
||||
)
|
||||
{
|
||||
return node is TypeDeclarationSyntax typeDeclaration && HasAttributedMembers(typeDeclaration);
|
||||
}
|
||||
|
||||
public static PanelStateIncrementalGeneratorTypeTarget? Transform(
|
||||
GeneratorSyntaxContext context,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (context.Node is not TypeDeclarationSyntax typeDeclaration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context.SemanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken) is not INamedTypeSymbol typeSymbol)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> markedMembers = CollectMarkedMembers(typeSymbol);
|
||||
if (markedMembers.IsDefaultOrEmpty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PanelStateIncrementalGeneratorTypeTarget(
|
||||
typeSymbol,
|
||||
typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||
markedMembers);
|
||||
}
|
||||
|
||||
private static bool HasAttributedMembers(
|
||||
TypeDeclarationSyntax typeDeclaration
|
||||
)
|
||||
{
|
||||
foreach (MemberDeclarationSyntax member in typeDeclaration.Members)
|
||||
{
|
||||
switch (member)
|
||||
{
|
||||
case FieldDeclarationSyntax { AttributeLists.Count: > 0 }:
|
||||
case PropertyDeclarationSyntax { AttributeLists.Count: > 0 }:
|
||||
case MethodDeclarationSyntax { AttributeLists.Count: > 0 }:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> CollectMarkedMembers(
|
||||
INamedTypeSymbol typeSymbol
|
||||
)
|
||||
{
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember>.Builder builder =
|
||||
ImmutableArray.CreateBuilder<PanelStateIncrementalGeneratorMarkedMember>();
|
||||
|
||||
foreach (ISymbol member in typeSymbol.GetMembers())
|
||||
{
|
||||
foreach (AttributeData attributeData in member.GetAttributes())
|
||||
{
|
||||
string? metadataName = attributeData.AttributeClass?.ToDisplayString();
|
||||
if (!PanelStateIncrementalGeneratorKnownAttributes.IsKnown(metadataName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Add(
|
||||
new PanelStateIncrementalGeneratorMarkedMember(
|
||||
member,
|
||||
metadataName!,
|
||||
member.Name,
|
||||
GetSymbolTypeName(member)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
private static string GetSymbolTypeName(
|
||||
ISymbol symbol
|
||||
)
|
||||
{
|
||||
return symbol switch
|
||||
{
|
||||
IFieldSymbol fieldSymbol => fieldSymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||
IPropertySymbol propertySymbol => propertySymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||
IMethodSymbol methodSymbol => methodSymbol.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||
_ => symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace StgSharp.UserInterface.Generator;
|
||||
|
||||
internal static class PanelStateIncrementalGeneratorStateChangeBindings
|
||||
{
|
||||
public static StateChangeBindingPartAnalysis Analyze(
|
||||
PanelStateIncrementalGeneratorTypeTarget target
|
||||
)
|
||||
{
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> members = target.MarkedMembers
|
||||
.Where(static member => member.AttributeMetadataName == PanelStateIncrementalGeneratorKnownAttributes.StateChangeBindingAttribute)
|
||||
.ToImmutableArray();
|
||||
|
||||
return new StateChangeBindingPartAnalysis(
|
||||
target.TypeDisplayName,
|
||||
members);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace StgSharp.UserInterface.Generator;
|
||||
|
||||
internal static class PanelStateIncrementalGeneratorStateCommandHandlers
|
||||
{
|
||||
public static StateChangeCommandHandlerPartAnalysis Analyze(
|
||||
PanelStateIncrementalGeneratorTypeTarget target,
|
||||
PanelStateSymbolPartAnalysis stateSymbols,
|
||||
StateChangeBindingPartAnalysis stateChangeBindings
|
||||
)
|
||||
{
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> members = MergeMembers(
|
||||
stateChangeBindings.Members,
|
||||
stateSymbols.Members);
|
||||
|
||||
return new StateChangeCommandHandlerPartAnalysis(
|
||||
target.TypeDisplayName,
|
||||
members);
|
||||
}
|
||||
|
||||
private static ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> MergeMembers(
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> primaryMembers,
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> secondaryMembers
|
||||
)
|
||||
{
|
||||
if (primaryMembers.IsDefaultOrEmpty && secondaryMembers.IsDefaultOrEmpty)
|
||||
{
|
||||
return ImmutableArray<PanelStateIncrementalGeneratorMarkedMember>.Empty;
|
||||
}
|
||||
|
||||
HashSet<string> seen = new(StringComparer.Ordinal);
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember>.Builder builder =
|
||||
ImmutableArray.CreateBuilder<PanelStateIncrementalGeneratorMarkedMember>();
|
||||
|
||||
AddMembers(builder, seen, primaryMembers);
|
||||
AddMembers(builder, seen, secondaryMembers);
|
||||
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
private static void AddMembers(
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember>.Builder builder,
|
||||
HashSet<string> seen,
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> members
|
||||
)
|
||||
{
|
||||
foreach (PanelStateIncrementalGeneratorMarkedMember member in members)
|
||||
{
|
||||
string key = member.Symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||
if (!seen.Add(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
builder.Add(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace StgSharp.UserInterface.Generator;
|
||||
|
||||
internal static class PanelStateIncrementalGeneratorStateSymbols
|
||||
{
|
||||
public static PanelStateSymbolPartAnalysis Analyze(
|
||||
PanelStateIncrementalGeneratorTypeTarget target
|
||||
)
|
||||
{
|
||||
ImmutableArray<PanelStateIncrementalGeneratorMarkedMember> members = target.MarkedMembers
|
||||
.Where(static member => PanelStateIncrementalGeneratorKnownAttributes.IsStateSymbolAttribute(member.AttributeMetadataName))
|
||||
.ToImmutableArray();
|
||||
|
||||
return new PanelStateSymbolPartAnalysis(
|
||||
target.TypeDisplayName,
|
||||
members);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace StgSharp.UserInterface.Generator;
|
||||
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public sealed class PanelStateIncrementalGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
IncrementalValuesProvider<PanelStateIncrementalGeneratorTypeTarget> typeTargets = context.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
static (node, _) => PanelStateIncrementalGeneratorPipeline.IsCandidate(node),
|
||||
static (syntaxContext, cancellationToken) =>
|
||||
PanelStateIncrementalGeneratorPipeline.Transform(syntaxContext, cancellationToken))
|
||||
.Where(static target => target is not null)
|
||||
.Select(static (target, _) => target!);
|
||||
|
||||
IncrementalValueProvider<ImmutableArray<PanelStateIncrementalGeneratorTypeTarget>> collectedTypeTargets = typeTargets.Collect();
|
||||
|
||||
IncrementalValueProvider<PanelStateIncrementalGeneratorInput> generatorInput = context.CompilationProvider
|
||||
.Combine(collectedTypeTargets)
|
||||
.Select(
|
||||
static (pair, _) => new PanelStateIncrementalGeneratorInput(
|
||||
pair.Left,
|
||||
pair.Right));
|
||||
|
||||
IncrementalValueProvider<PanelStateIncrementalGeneratorOutput> generatorOutput = generatorInput
|
||||
.Select(
|
||||
static (input, _) => PanelStateIncrementalGeneratorAnalysis.Analyze(input));
|
||||
|
||||
context.RegisterSourceOutput(
|
||||
generatorOutput,
|
||||
static (productionContext, source) =>
|
||||
PanelStateIncrementalGeneratorEmitter.Emit(productionContext, source));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Runtime.CompilerServices;
|
||||
global using System.Runtime.InteropServices;
|
||||
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
|
||||
namespace StgSharp.UserInterface.Sample
|
||||
{
|
||||
public sealed class PanelStateSample : PanelState
|
||||
{
|
||||
public const string SampleStatePath = nameof(PanelStateSample) + "." + nameof(SampleState);
|
||||
|
||||
private int sampleState;
|
||||
|
||||
public PanelStateSample()
|
||||
: base(nameof(PanelStateSample))
|
||||
{
|
||||
}
|
||||
|
||||
public int SampleState => sampleState;
|
||||
|
||||
// 生成器拆分后,这里相当于某个节点 handler 的 guard,
|
||||
// 对应 ICommand 语义中的 CanExecute。
|
||||
private static bool CanApplySampleStateChange(
|
||||
int origin,
|
||||
int current,
|
||||
StateValueCommand<int> command
|
||||
)
|
||||
{
|
||||
return string.Equals(command.StatePath, SampleStatePath, StringComparison.Ordinal)
|
||||
&& current >= 0
|
||||
&& origin != current;
|
||||
}
|
||||
|
||||
// 实际项目里这整个 StateChangeHandler 都由源生成器产出;
|
||||
// 这里手写展开,只是为了展示“没有生成器时 VSS 代码长什么样”。
|
||||
public void ApplySampleStateChangeCommand(
|
||||
StateValueCommand<int> command
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
|
||||
int origin = sampleState;
|
||||
int current = command.Value;
|
||||
if (!CanApplySampleStateChange(origin, current, command))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sampleState = current;
|
||||
BroadcastSampleStateChanged(origin, current);
|
||||
}
|
||||
|
||||
// 生成器拆分后,这里相当于某个节点写入后的 post-change hook,
|
||||
// 对应手写体系里常见的 OnChange 位置。
|
||||
private static void BroadcastSampleStateChanged(
|
||||
int origin,
|
||||
int current
|
||||
)
|
||||
{
|
||||
_ = origin;
|
||||
_ = current;
|
||||
|
||||
// event 已禁用。手写版本若还需要状态后置联动,
|
||||
// 这里只能继续手写内部同步/广播逻辑;正式版本由生成器织入。
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.UnserInterface.Service
|
||||
{
|
||||
internal interface IService
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="IState"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.UnserInterface.State
|
||||
{
|
||||
internal class IState { }
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk" />
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<RequiresNativeRuntime>true</RequiresNativeRuntime>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
|
||||
<CommonManagedOutput>$(SourceRoot)StgSharp.Common\bin\$(Platform)\$(Configuration)\$(TargetFramework)\StgSharp.Common.dll</CommonManagedOutput>
|
||||
<CommonManagedPdb>$(SourceRoot)StgSharp.Common\bin\$(Platform)\$(Configuration)\$(TargetFramework)\StgSharp.Common.pdb</CommonManagedPdb>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StgSharp.Common\StgSharp.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="$(OutputPath)$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
|
||||
<None Include="$(OutputPath)$(AssemblyName).pdb" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" Condition="Exists('$(OutputPath)$(AssemblyName).pdb')" />
|
||||
<None Include="$(CommonManagedOutput)" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" Condition="Exists('$(CommonManagedOutput)')" />
|
||||
<None Include="$(CommonManagedPdb)" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" Condition="Exists('$(CommonManagedPdb)')" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+209
-371
@@ -3,473 +3,311 @@
|
||||
> **作者**:Nitload-NSI
|
||||
> **创建日期**:2026-03-17
|
||||
> **所属项目**:StgSharp 框架 / 工业上位机架构预演
|
||||
> **文档状态**:初稿
|
||||
> **文档状态**:精简版草案
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
## 1. 文档定位
|
||||
|
||||
1. [背景与动机](#1-背景与动机)
|
||||
2. [核心思想](#2-核心思想)
|
||||
3. [架构总览](#3-架构总览)
|
||||
4. [V 层:I/O 适配层](#4-v-层io-适配层)
|
||||
5. [S 层:状态机](#5-s-层状态机)
|
||||
6. [S 层:服务层](#6-s-层服务层)
|
||||
7. [层间通信协议](#7-层间通信协议)
|
||||
8. [与 MVVM 的对比](#8-与-mvvm-的对比)
|
||||
9. [适用场景](#9-适用场景)
|
||||
10. [参考实现索引](#10-参考实现索引)
|
||||
本文档用于说明 VSS 的整体架构思想、三层职责划分与层间关系。
|
||||
具体类型定义、命名与边界,以《VSS 三层类型基座设计草案》为准。
|
||||
|
||||
当前版本需要特别强调一点:
|
||||
State 层不再被描述为几个并列的中枢对象,而是对外统一表现为一个单例 `StateHost`。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与动机
|
||||
## 2. 设计评估
|
||||
|
||||
### 1.1 MVVM 的局限
|
||||
把整个 State 层对外收敛为 `StateHost` 单例,是比旧描述更合理的做法。
|
||||
|
||||
MVVM(Model-View-ViewModel)是一种面向"轻型商用软件"设计的 UI 架构模式。其隐含前提为:
|
||||
原因有三点:
|
||||
|
||||
- **Model 是被动的数据容器**,不具备独立生命周期
|
||||
- **ViewModel 是核心调度者**,负责连接 View 与 Model
|
||||
- **View 由数据变化驱动**(双向绑定)
|
||||
- 它明确了唯一中枢,避免旧版文档中的多个中枢概念同时被读成并列核心。
|
||||
- 它明确了所有权,说明命令接入、状态抽象和服务管理都属于同一个运行时边界。
|
||||
- 它更符合宿主型系统的实际组织方式,对外暴露一个主机,对内再展开结构,比暴露多个概念性中枢更稳定。
|
||||
|
||||
这一前提在以下场景中失效:
|
||||
这个设计也有一个边界需要保留:
|
||||
虽然 `ServiceManager` 现在被放入 `StateHost` 的下层,但这并不意味着状态与能力本体被混为一层。
|
||||
`AppState` 仍然是状态抽象,`ServiceManager` 仍然只是服务管理结构,具体服务对象依然属于能力层。
|
||||
|
||||
| 场景 | 失效原因 |
|
||||
|---|---|
|
||||
| 工业上位机 | 设备/硬件具有独立生命周期和状态机,不是"被动数据" |
|
||||
| 仿真集群软件 | 计算节点有自己的异步事件流,不受 ViewModel 调度 |
|
||||
| 多输入源 HMI | 鼠标、专用手柄、实体按钮等多源输入在 View 层打架 |
|
||||
| 重型可视化软件 | ViewModel 膨胀为"上帝类",承担了所有业务逻辑 |
|
||||
|
||||
### 1.2 直接动机
|
||||
|
||||
本架构的直接动机来源于维护一套 WinForms + MVP 上位机项目时,**鼠标与专用手柄两种输入模式相互干扰**的问题。
|
||||
|
||||
解决思路的核心转变:
|
||||
|
||||
> **外设事件不应提交给 View,而应提交给 State。**
|
||||
> View 只负责将当前 State 状态呈现出来。
|
||||
|
||||
这一转变最终演化为完整的 VSS 架构。
|
||||
|
||||
### 1.3 设计血统
|
||||
|
||||
VSS 的部分设计思路借鉴自**游戏引擎架构**,核心哲学为:
|
||||
|
||||
> **以输入为核心,以逻辑为驱动,界面显示是被动的副产品。**
|
||||
|
||||
这与 Unity New Input System、虚幻引擎 Enhanced Input 等现代游戏输入系统的设计理念一脉相承。
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心思想
|
||||
|
||||
### 2.1 两个根本原则
|
||||
|
||||
**原则一:所有"意图"必须经过 State 归一化**
|
||||
|
||||
```
|
||||
鼠标点击 ──┐
|
||||
手柄按键 ──┼──→ State.Dispatch(Intent) ──→ 统一的状态转移逻辑
|
||||
实体按钮 ──┤
|
||||
网络指令 ──┘
|
||||
```
|
||||
|
||||
无论信号来源如何,进入 State 之前都转化为统一的 `Intent`。State 是**唯一的语义解释者**。
|
||||
|
||||
**原则二:View 层可以完全换掉或关闭**
|
||||
|
||||
```
|
||||
有头模式: V(WPF) + State + Service (操作员在场)
|
||||
无头模式: State + Service (自动化运行/集群节点)
|
||||
远程模式: V(薄客户端) + State + Service (远程接管显示)
|
||||
HMI 模式: V(实体面板) + State + Service (无屏幕操作台)
|
||||
```
|
||||
|
||||
State 和 Service 在任何模式下**零改动**。
|
||||
|
||||
### 2.2 职责边界的时间维度
|
||||
|
||||
不同层次的操作具有根本不同的时间特征,这是分层的真正依据:
|
||||
|
||||
| 层 | 时间特征 | 驱动方式 |
|
||||
|---|---|---|
|
||||
| V(渲染输出) | 帧级,毫秒级刷新 | 时钟主动驱动 |
|
||||
| V(输入采集) | 事件级,异步到达 | 中断/回调驱动 |
|
||||
| State | 事件队列消费,微秒~毫秒 | 串行队列驱动 |
|
||||
| Service | 业务流程,毫秒~分钟 | Command 触发 |
|
||||
| 设备/集群节点 | 独立生命周期,跨越整个运行期 | 自驱动 |
|
||||
|
||||
让 ViewModel 同时承担所有时间维度的调度,是 MVVM 在重型软件中失败的根本原因。
|
||||
另外,前侧命令链路只定义命令而不定义结果也是不完整的。
|
||||
如果 `FrontCommandHost` 已经区分 `StateChangeCommand` 与 `ExecuteCommand`,那么还必须存在统一的 `CommandResult`,用于把执行完成状态回传给 View。
|
||||
|
||||
---
|
||||
|
||||
## 3. 架构总览
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ V — View Layer(I/O 适配层集合) │
|
||||
│ │
|
||||
│ ┌─────────────────────────┐ ┌────────────────────────┐ │
|
||||
│ │ 输入适配器 │ │ 输出适配器 │ │
|
||||
│ │ IInputAdapter │ │ IOutputAdapter │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ · WpfInputAdapter │ │ · WpfPresenter │ │
|
||||
│ │ 鼠标/键盘/触摸 │ │ 帧时钟驱动 XAML 刷新 │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ · HmiInputAdapter │ │ · HmiOutputAdapter │ │
|
||||
│ │ 实体按钮/旋钮/急停 │ │ LED/炫彩/面板小屏 │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ · DeviceInputAdapter │ │ · NullOutputAdapter │ │
|
||||
│ │ 专用手柄/外接设备 │ │ 无头模式,丢弃输出 │ │
|
||||
│ └──────────┬──────────────┘ └───────────┬────────────┘ │
|
||||
└─────────────┼──────────────────────────────┼───────────────┘
|
||||
│ Intent(原始输入,无语义) │ ← Snapshot(只读快照)
|
||||
↓ ↑
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ S — State(抽象状态机) │
|
||||
│ │
|
||||
│ · 串行 Intent 队列,保证状态转移原子性 │
|
||||
│ · 持有所有 UI 可见状态,生成 Snapshot 通知 V 层 │
|
||||
│ · 接收所有输入源:V 层 Intent + Service Event │
|
||||
│ · 向 Service 发送 Command(携带完整上下文) │
|
||||
│ · 不持有 Service 具体实现引用(依赖接口) │
|
||||
│ · 不知道 V 层的存在 │
|
||||
└───────────────────────────┬─────────────────────────────────┘
|
||||
│ Command(有语义指令,携带完整上下文)
|
||||
↕ Event(异步完成通知)
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ S — Service Layer(业务服务层) │
|
||||
│ │
|
||||
│ · 每个 Service 拥有独立生命周期和内部状态机 │
|
||||
│ · 执行具体业务:设备控制、集群调度、数据持久化、通信协议等 │
|
||||
│ · 通过 Event 将异步结果回传给 State │
|
||||
│ · 完全不知道 V 层的存在 │
|
||||
│ · Command 应自描述,Service 执行时无需回调 State │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
VSS 当前建议的核心结构如下:
|
||||
|
||||
View / Electron / HMI / 外设 / 其他宿主
|
||||
↓
|
||||
StateHost(单例)
|
||||
├─ 上层:FrontCommandHost
|
||||
├─ 中层:AppState
|
||||
└─ 下层:ServiceManager
|
||||
↓
|
||||
Service / ServiceHost / 具体能力实例
|
||||
|
||||
这个结构表达的是:
|
||||
|
||||
1. 对外,State 层只有一个统一入口,即 `StateHost`。
|
||||
2. 对内,`StateHost` 全息展开为上中下三层。
|
||||
3. 服务能力通过 `ServiceManager` 被纳入统一调度,而不是直接散落在外部结构中。
|
||||
|
||||
其基本流程可以概括为:
|
||||
|
||||
1. 外部宿主产生交互或控制请求。
|
||||
2. 请求进入 `StateHost`。
|
||||
3. `StateHost` 的上层 `FrontCommandHost` 接收并区分 `StateChangeCommand` 与 `ExecuteCommand`。
|
||||
4. `StateHost` 的中层 `AppState` 参与状态判断与状态更新。
|
||||
5. 当命令涉及能力执行时,`StateHost` 的下层 `ServiceManager` 负责调度服务。
|
||||
6. 命令处理完成后,`StateHost` 统一生成 `CommandResult` 回传给 View。
|
||||
7. 状态变化与结果通知分别承担状态同步和执行完成通知两类职责。
|
||||
|
||||
---
|
||||
|
||||
## 4. V 层:I/O 适配层
|
||||
## 4. View 层
|
||||
|
||||
### 4.1 重新定义"View"
|
||||
### 4.1 定位
|
||||
|
||||
VSS 中的 V **不是"界面"**,而是**"所有与外部世界的 I/O 适配层的集合"**。
|
||||
View 层是前侧交互来源的统一抽象。
|
||||
它不等同于传统窗口或页面,而是所有外部交互入口与宿主环境的集合。
|
||||
|
||||
```
|
||||
V 层 = 输入适配器集合 ∪ 输出适配器集合
|
||||
```
|
||||
View 层只负责两件事:
|
||||
|
||||
这一定义来源于对"实体 HMI 也是一种 View"的认识:
|
||||
- 承接交互
|
||||
- 将交互转化为命令并送入 `StateHost`
|
||||
|
||||
- 实体按钮面板:有输入(按键),有输出(指示灯、小屏幕、炫彩灯带)
|
||||
- WPF 窗口:有输入(鼠标/键盘/触摸),有输出(屏幕渲染)
|
||||
- 无头节点:无 V 层,State + Service 直接运行
|
||||
同时,View 还需要能够接收由 `StateHost` 回传的 `CommandResult`,从而知道某次执行型命令是否已经完成。
|
||||
|
||||
### 4.2 输入适配器(IInputAdapter)
|
||||
它不再承担中枢职责,也不直接持有服务调度责任。
|
||||
|
||||
```csharp
|
||||
public interface IInputAdapter
|
||||
{
|
||||
void Start(Action<Intent> dispatch);
|
||||
void Stop();
|
||||
}
|
||||
```
|
||||
### 4.2 基础类型
|
||||
|
||||
职责:
|
||||
- 监听来自特定输入源的原始信号
|
||||
- 将原始信号转化为 `Intent`(**不做语义判断**)
|
||||
- 通过 `dispatch` 委托投递到 State 的事件队列
|
||||
#### ViewHost
|
||||
|
||||
**关键约束**:输入适配器只描述"发生了什么",不判断"应该做什么"。
|
||||
`ViewHost` 是前侧宿主的统一抽象,用于表示任何可以承接界面表现、输入来源或外部交互来源的宿主对象。
|
||||
|
||||
### 4.3 输出适配器(IOutputAdapter)与 Presenter
|
||||
#### StateChangeCommand
|
||||
|
||||
```csharp
|
||||
public interface IOutputAdapter
|
||||
{
|
||||
void Start();
|
||||
void Stop();
|
||||
void AcceptSnapshot(StateSnapshot snapshot); // 可能在任意线程调用
|
||||
}
|
||||
```
|
||||
`StateChangeCommand` 用于表达对系统状态的直接修改请求。
|
||||
|
||||
**Presenter 是输出适配器的一种**,负责屏幕渲染输出。
|
||||
#### ExecuteCommand
|
||||
|
||||
WPF 环境下,Presenter 挂载于 `CompositionTarget.Rendering`——这是 WPF 暴露的帧循环钩子,与显示器刷新率同步(通常 60fps)。
|
||||
`ExecuteCommand` 用于表达需要进入执行流程的命令请求。
|
||||
|
||||
```
|
||||
State.Commit()
|
||||
↓ AcceptSnapshot()(任意线程,原子写 _pending)
|
||||
CompositionTarget.Rendering(WPF 帧时钟,UI线程)
|
||||
↓ 检测 _pending 变化
|
||||
↓ PushToView()
|
||||
View.DataContext = snapshot(触发 XAML 绑定刷新)
|
||||
```
|
||||
#### CommandResult
|
||||
|
||||
**帧合并机制**:State 在两帧之间可能产生多个 Snapshot(如高频设备数据更新),Presenter 的帧循环只取最新的 `_pending`,自动合并中间状态,保持渲染频率稳定。
|
||||
`CommandResult` 用于表达某次前侧命令处理完成后的统一结果通知。
|
||||
|
||||
非屏幕输出适配器(如 HMI 灯带)使用独立低频定时器,无需挂载帧时钟。
|
||||
### 4.3 小结
|
||||
|
||||
### 4.4 ViewLayer 容器
|
||||
View 层的职责不是承载业务逻辑,而是把复杂、多样、异构的交互来源收敛成稳定命令,然后统一送入 `StateHost`。
|
||||
|
||||
```csharp
|
||||
public class ViewLayer
|
||||
{
|
||||
public ViewLayer Add(IInputAdapter adapter);
|
||||
public ViewLayer Add(IOutputAdapter adapter);
|
||||
public void Start(Action<Intent> dispatch);
|
||||
public void Stop();
|
||||
public void AcceptSnapshot(StateSnapshot snapshot); // 广播给所有输出适配器
|
||||
}
|
||||
```
|
||||
|
||||
V 层对外只暴露两个接口:
|
||||
- `Start(dispatch)`:启动所有适配器,传入 State 的 Dispatch 入口
|
||||
- `AcceptSnapshot(snapshot)`:接收新状态,广播给所有输出适配器
|
||||
|
||||
### 4.5 WPF 中的 XAML 规范
|
||||
|
||||
在 VSS 架构下,WPF 的 XAML 层应遵循以下规范:
|
||||
|
||||
```
|
||||
✅ 允许:
|
||||
· 纯布局声明(Grid、StackPanel、Border 等)
|
||||
· DataContext 绑定({Binding PropertyName},单向或双向均可)
|
||||
· Code-behind 中的事件转发(OnXxxClicked → Dispatch(Intent))
|
||||
|
||||
❌ 禁止:
|
||||
· XAML 中的 Command 绑定(ICommand、RelayCommand 等)
|
||||
· Code-behind 中的业务逻辑判断
|
||||
· ViewModel 类(无 ViewModel 层)
|
||||
· Code-behind 直接调用 Service
|
||||
```
|
||||
|
||||
Code-behind 的唯一职责:
|
||||
|
||||
```csharp
|
||||
// 用户操作 → 转发为 Intent,不做任何判断
|
||||
private void OnPrintClicked(object s, RoutedEventArgs e)
|
||||
=> _dispatch?.Invoke(new PrintRequestedIntent());
|
||||
```
|
||||
对于执行类命令,View 不能只依赖状态变化判断是否结束,还应通过 `CommandResult` 获得明确的完成通知。
|
||||
|
||||
---
|
||||
|
||||
## 5. S 层:状态机
|
||||
## 5. State 层
|
||||
|
||||
### 5.1 职责定义
|
||||
### 5.1 定位
|
||||
|
||||
State 是 VSS 的核心,也是**唯一持有应用状态的地方**。
|
||||
State 层是 VSS 的唯一状态与调度中枢。
|
||||
它对外统一表现为 `StateHost` 单例,对内展开为三层结构。
|
||||
|
||||
```
|
||||
State 做且只做:
|
||||
1. 接收 Intent
|
||||
2. 决策状态如何转移
|
||||
3. 向 Service 发 Command
|
||||
4. 生成新 Snapshot 通知 V 层
|
||||
```
|
||||
### 5.2 内部结构
|
||||
|
||||
State **不做**:
|
||||
- 任何 IO 操作
|
||||
- 任何耗时计算
|
||||
- 直接操作 UI 控件
|
||||
- 持有 Service 的具体实现(依赖接口)
|
||||
#### StateHost
|
||||
|
||||
### 5.2 串行事件队列
|
||||
`StateHost` 是 State 层唯一对外暴露的运行时主机。
|
||||
|
||||
State 内部维护一个串行消费队列,**所有输入源**(V 层 Intent + Service Event)都通过同一个队列进入:
|
||||
它负责:
|
||||
|
||||
```
|
||||
[WPF鼠标事件] ──┐
|
||||
[手柄按键] ──┤
|
||||
[实体按钮] ──┼──→ Channel<Intent> ──→ State(单线程消费)
|
||||
[Service完成] ──┤
|
||||
[定时器触发] ──┘
|
||||
```
|
||||
- 接收来自外部宿主的命令
|
||||
- 统一组织命令接入、状态抽象和服务管理
|
||||
- 统一处理状态同步、事件广播、服务结果回流和 `CommandResult` 回传
|
||||
|
||||
**意义**:状态转移是原子的,不会因并发输入导致内部状态撕裂。这是简化版的 **Actor 模型**。
|
||||
#### 上层:FrontCommandHost
|
||||
|
||||
### 5.3 StateSnapshot
|
||||
`FrontCommandHost` 是 `StateHost` 的上层,用于承接前侧命令输入。
|
||||
它属于 State 内部结构,不再被视为 View 层自己的中介中心。
|
||||
|
||||
Snapshot 是 State 暴露给 V 层的**只读数据快照**,使用 C# `record` 定义以保证不可变性:
|
||||
在命令协议上,`FrontCommandHost` 至少要面对两类前向命令:
|
||||
|
||||
```csharp
|
||||
public record StateSnapshot
|
||||
{
|
||||
// 所有字段均为 init-only,外部无法修改
|
||||
public bool IsBusy { get; init; }
|
||||
public string StatusMessage { get; init; } = "";
|
||||
// ... 其他字段
|
||||
}
|
||||
```
|
||||
- `StateChangeCommand`
|
||||
- `ExecuteCommand`
|
||||
|
||||
State 内部通过 `with` 表达式生成新 Snapshot:
|
||||
对应地,State 在处理完成后还需要回传统一的 `CommandResult`。
|
||||
|
||||
```csharp
|
||||
Commit(_snapshot with { IsBusy = true, StatusMessage = "处理中..." });
|
||||
```
|
||||
#### 中层:AppState
|
||||
|
||||
### 5.4 Intent 与 Command 的区别
|
||||
`AppState` 是 `StateHost` 的中层状态抽象,是全局状态树根。
|
||||
它代表状态本体,而不是状态宿主。
|
||||
|
||||
| | Intent | Command |
|
||||
|---|---|---|
|
||||
| **方向** | V → State | State → Service |
|
||||
| **语义** | 无(描述事实) | 有(描述意图) |
|
||||
| **产生者** | V 层适配器 | State 状态机 |
|
||||
| **消费者** | State | Service |
|
||||
| **示例** | `MouseClickedIntent(x, y)` | `StartSimulationCommand(config)` |
|
||||
#### 下层:ServiceManager
|
||||
|
||||
**同一个 Command 可以由多种 Intent 触发**,这是多输入源统一的关键。
|
||||
`ServiceManager` 是 `StateHost` 的下层服务管理结构。
|
||||
它负责把命令向下转化为具体能力调度,并将结果回流状态体系。
|
||||
|
||||
#### 配套结构
|
||||
|
||||
State 层还包括:
|
||||
|
||||
- `PanelState`,用于表示局部状态单元
|
||||
|
||||
此外,observer 语义仍然存在,但在 `StgSharp.UserInterface` 中不再通过 `OnStateChanged` / `StateChangeObserver` 这类手写公共类型表达。
|
||||
|
||||
这里更准确的说法是:状态写入后的轻量 observer 逻辑,改为由源生成器围绕具体状态节点直接分布式织入。
|
||||
|
||||
在传统 MVVM 中,observer 往往同时承担:
|
||||
|
||||
- 内部逻辑回调
|
||||
- VM 内部广播变化
|
||||
- 通知 View 重绘
|
||||
- 通知 Data / 业务执行
|
||||
|
||||
而在 VSS 中,这四项职责被拆开处理:
|
||||
|
||||
- 内部逻辑回调:由 Service 或具体业务实现承担
|
||||
- VM 内部广播变化:由源生成器生成的轻量 observer hook 承担
|
||||
- 通知 View 重绘:由 command/result 与同步符号链路承担
|
||||
- 通知 Data / 业务执行:由 command/result 与 service 调度承担
|
||||
|
||||
其中 `CommandResult` 仍用于通知某次命令已经处理完成。
|
||||
|
||||
### 5.3 小结
|
||||
|
||||
State 层现在应被理解为一个统一主机,而不是多个同级中心。
|
||||
`StateHost` 对外是单例边界,对内则由 `FrontCommandHost`、`AppState` 和 `ServiceManager` 三层共同构成。
|
||||
|
||||
---
|
||||
|
||||
## 6. S 层:服务层
|
||||
## 6. Service 层
|
||||
|
||||
### 6.1 职责定义
|
||||
### 6.1 定位
|
||||
|
||||
Service 负责所有"重活":
|
||||
Service 层仍然是系统能力的承载层,但其管理入口已经被明确收敛到 `StateHost` 的下层 `ServiceManager`。
|
||||
|
||||
- 设备驱动与通信协议
|
||||
- 集群节点调度
|
||||
- 数据持久化与查询
|
||||
- 耗时计算与文件 IO
|
||||
- 网络请求
|
||||
因此,Service 层关注的重点是:
|
||||
|
||||
Service 的核心特征:**有自己独立的生命周期和内部状态机**,不受 State 控制,只响应 Command。
|
||||
- 被调度的具体服务对象
|
||||
- 服务装载与完成事件
|
||||
- 服务运行宿主与能力实例
|
||||
|
||||
### 6.2 Command 自描述原则
|
||||
Service 内部完成事件可以回流到 `StateHost`,但前侧最终接收到的仍应是统一的 `CommandResult`,而不是直接暴露服务内部事件。
|
||||
|
||||
Command 应携带执行所需的**完整上下文**,Service 执行时无需回调 State 查询任何信息:
|
||||
### 6.2 基础类型
|
||||
|
||||
```csharp
|
||||
// ❌ 错误设计:Service 执行到一半还要问 State
|
||||
service.Execute(cmd);
|
||||
// Service 内部:var config = state.GetCurrentConfig(); ← 违反原则
|
||||
#### ServiceLoadCommand
|
||||
|
||||
// ✅ 正确设计:Command 是自描述的
|
||||
service.Execute(new StartSimulationCommand
|
||||
{
|
||||
NodeCount = 8,
|
||||
ConfigSnapshot = currentConfig, // 执行时的完整上下文快照
|
||||
ResultEvent = typeof(SimulationStartedEvent) // 完成后回传的 Event 类型
|
||||
});
|
||||
```
|
||||
`ServiceLoadCommand` 用于表达服务装载请求。
|
||||
|
||||
**意义**:当某个 Service 大到需要拆分为独立微服务时,Command 的接口几乎不需要修改——这是架构可扩展性的基础。
|
||||
#### ServiceLoadAccomplishmentEvent
|
||||
|
||||
### 6.3 Service 与 State 解耦
|
||||
`ServiceLoadAccomplishmentEvent` 用于表达服务装载完成后的结果通知。
|
||||
|
||||
```
|
||||
State → Service:Command(State 主动发起)
|
||||
Service → State:Event(Service 异步回传)
|
||||
#### ServiceHost
|
||||
|
||||
Service 持有 State 的 Dispatch 委托(或事件总线)
|
||||
Service 不持有 State 的引用
|
||||
State 持有 Service 的接口引用
|
||||
Service 完全不知道 V 层的存在
|
||||
```
|
||||
`ServiceHost` 用于表达某一类服务实例的直接宿主或执行载体。
|
||||
|
||||
### 6.3 小结
|
||||
|
||||
`ServiceManager` 属于 State 的下层管理结构,而 `ServiceHost` 与具体服务实例才属于能力层主体。
|
||||
这样可以在统一调度和能力解耦之间保持清晰边界。
|
||||
|
||||
---
|
||||
|
||||
## 7. 层间通信协议
|
||||
## 7. 层间关系
|
||||
|
||||
### 7.1 完整数据流
|
||||
### 7.1 View → State
|
||||
|
||||
```
|
||||
[用户操作 / 外设信号]
|
||||
↓
|
||||
IInputAdapter.OnXxx()
|
||||
↓ dispatch(Intent)
|
||||
State 的串行 Channel<Intent>
|
||||
↓ HandleIntent(intent)
|
||||
State 内部状态转移
|
||||
↓ Commit(newSnapshot)
|
||||
IOutputAdapter.AcceptSnapshot() ← 可能在非 UI 线程
|
||||
↓ (各适配器自行处理线程安全)
|
||||
屏幕渲染 / LED 更新 / 面板刷新 ← 各自的驱动时钟
|
||||
```
|
||||
View 层通过命令把交互送入 `StateHost`。
|
||||
命令首先由 `FrontCommandHost` 接入,再由 `StateHost` 统一协调后续动作。
|
||||
当前前侧协议至少应包含:
|
||||
|
||||
### 7.2 线程模型
|
||||
- `StateChangeCommand`
|
||||
- `ExecuteCommand`
|
||||
- `CommandResult`
|
||||
|
||||
| 操作 | 线程 |
|
||||
|---|---|
|
||||
| `IInputAdapter` 事件采集 | 各自线程(WPF UI 线程 / 后台轮询线程) |
|
||||
| `State.Dispatch(Intent)` | 任意线程,线程安全(Channel 写入是原子的) |
|
||||
| `State` 内部消费循环 | 单一后台线程 |
|
||||
| `IOutputAdapter.AcceptSnapshot()` | State 的消费线程(非 UI 线程) |
|
||||
| `WpfPresenter.PushToView()` | WPF UI 线程(由 `CompositionTarget.Rendering` 保证) |
|
||||
| `HmiOutputAdapter.OnTick()` | Timer 回调线程 |
|
||||
### 7.2 State 内部关系
|
||||
|
||||
**核心结论**:整个架构中**只有 WpfPresenter 需要关心 UI 线程**,其余所有层次天然线程安全,无需到处写 `Dispatcher.Invoke`。
|
||||
State 内部的关系是:
|
||||
|
||||
### 7.3 组装示例
|
||||
- 上层 `FrontCommandHost` 负责接命令
|
||||
- 中层 `AppState` 负责持状态
|
||||
- 下层 `ServiceManager` 负责管服务
|
||||
|
||||
```csharp
|
||||
// Program / App.xaml.cs
|
||||
var window = new MainWindow();
|
||||
三者共同组成一个统一的 State 运行时主机。
|
||||
|
||||
// 组装 V 层
|
||||
var view = new ViewLayer()
|
||||
.Add(new WpfInputAdapter(window))
|
||||
.Add(new HmiInputAdapter(hmiDriver))
|
||||
.Add(new WpfPresenter(window))
|
||||
.Add(new HmiOutputAdapter(hmiDriver));
|
||||
### 7.3 State → Service
|
||||
|
||||
// 组装 Service 层
|
||||
var excelService = new ExcelService();
|
||||
var printerService = new PrinterService();
|
||||
// ...
|
||||
|
||||
// 组装 State
|
||||
var state = new AppState(view.AcceptSnapshot, excelService, printerService);
|
||||
|
||||
// 启动
|
||||
view.Start(state.Dispatch);
|
||||
window.Show();
|
||||
```
|
||||
当命令涉及能力执行时,`StateHost` 通过下层 `ServiceManager` 调度服务对象。
|
||||
服务执行结果再统一回流 `StateHost`,由其决定状态更新与通知广播。
|
||||
对于 View 而言,某次执行是否完成,应以 `CommandResult` 为准,而不是直接依赖服务内部事件。
|
||||
|
||||
---
|
||||
|
||||
## 8. 与 MVVM 的对比
|
||||
## 8. 与 MVVM 的区别
|
||||
|
||||
| 维度 | MVVM | VSS |
|
||||
|---|---|---|
|
||||
| **核心驱动** | 数据变化驱动视图 | 输入驱动状态,状态驱动视图 |
|
||||
| **View 的角色** | 双向绑定的参与者 | 纯粹的 I/O 适配层 |
|
||||
| **逻辑入口** | ICommand 散落在 ViewModel | 统一的 Intent → State |
|
||||
| **多输入源** | 无原生支持,需自行处理 | 天然归一化到 State |
|
||||
| **ViewModel/State 职责** | 常膨胀为上帝类 | State 只做状态转移决策 |
|
||||
| **业务复杂度承载** | Model 层,无规范 | Service 层,有明确生命周期规范 |
|
||||
| **线程模型** | 需大量 Dispatcher.Invoke | 只有 Presenter 需要关心 UI 线程 |
|
||||
| **View 可替换性** | View 和 VM 生命周期耦合 | View 层可任意替换或完全关闭 |
|
||||
| **无头运行** | 困难 | 天然支持(移除 V 层即可) |
|
||||
| **适合场景** | 纯前端、纯数据展示类应用 | 工业上位机、仿真集群、多 HMI 软件 |
|
||||
相较于 MVVM,VSS 更强调统一运行时主机,而不是围绕界面绑定建立系统中心。
|
||||
|
||||
主要区别在于:
|
||||
|
||||
- MVVM 通常以界面和 ViewModel 配合为中心,VSS 以 `StateHost` 为唯一中枢
|
||||
- MVVM 更适合界面驱动型应用,VSS 更适合多输入源、多宿主、强状态调度的软件
|
||||
- VSS 明确区分状态本体、命令接入和服务管理,但又把它们统一收束在同一个 State 边界内
|
||||
|
||||
---
|
||||
|
||||
## 9. 适用场景
|
||||
|
||||
### ✅ VSS 强烈推荐
|
||||
VSS 更适合以下场景:
|
||||
|
||||
- 工业上位机(需要驱动硬件设备)
|
||||
- 多输入源 HMI(鼠标 + 触摸 + 手柄 + 实体按钮)
|
||||
- 仿真计算软件(既可单机也可集群)
|
||||
- 需要"无头模式"运行的软件(自动化测试、远程节点)
|
||||
- 某个功能模块已大到可以单独拆出做服务的软件
|
||||
- 工业上位机
|
||||
- 多输入源 HMI
|
||||
- 仿真与控制软件
|
||||
- 支持无头运行的软件
|
||||
- 需要多宿主接入的系统
|
||||
|
||||
### ⚠️ MVVM 仍然适用
|
||||
|
||||
- 纯数据展示(Dashboard、报表工具)
|
||||
- 纯前端业务(表单填写、内容管理)
|
||||
- 业务逻辑简单,Model 没有独立生命周期
|
||||
|
||||
### ❌ 两者都不适合
|
||||
|
||||
- 分布式系统的后端服务(应使用 DDD / Clean Architecture)
|
||||
- 微服务内部逻辑(应使用领域模型 + CQRS)
|
||||
对于纯数据展示、轻量表单或前端页面驱动型应用,MVVM 仍可能是更直接的选择。
|
||||
|
||||
---
|
||||
|
||||
## 10. 当前建议保留的最小类型清单
|
||||
|
||||
*本文档随项目演进持续更新。架构思想的提出与设计版权归 Nitload 所有。*
|
||||
### View 层
|
||||
|
||||
- `ViewHost`
|
||||
- `StateChangeCommand`
|
||||
- `ExecuteCommand`
|
||||
- `CommandResult`
|
||||
|
||||
### State 层
|
||||
|
||||
- `StateHost`
|
||||
- `FrontCommandHost`
|
||||
- `AppState`
|
||||
- `PanelState`
|
||||
- `ServiceManager`
|
||||
|
||||
### Service 层
|
||||
|
||||
- `ServiceLoadCommand`
|
||||
- `ServiceLoadAccomplishmentEvent`
|
||||
- `ServiceHost`
|
||||
|
||||
---
|
||||
|
||||
## 11. 结论
|
||||
|
||||
VSS 的重点不在于先定义某一种具体界面实现,而在于先定义稳定的运行时骨架。
|
||||
在当前阶段,更准确的表达应当是:
|
||||
|
||||
- View 层负责交互来源
|
||||
- State 层对外统一表现为 `StateHost` 单例
|
||||
- `StateHost` 对内全息展开为 `FrontCommandHost`、`AppState`、`ServiceManager` 三层
|
||||
- 前侧命令协议由 `StateChangeCommand`、`ExecuteCommand`、`CommandResult` 构成闭环
|
||||
- Service 层负责承载被调度的具体能力对象
|
||||
|
||||
这一定义比旧版本更清楚地表达了唯一中枢、运行时边界和内部层次关系。
|
||||
@@ -0,0 +1,82 @@
|
||||
%% VSS 基座类型关系图
|
||||
%% 这份图优先表达“包含/从属关系”,其次才表达“交互关系”。
|
||||
%% 实线表示结构归属,虚线表示交互/消息流,不等同于当前代码里的具体成员实现。
|
||||
%% 整体按 View -> State -> Service 自上而下排列,避免横向摊开后破坏层次关系。
|
||||
%% observer 语义仍然存在,但在 UserInterface 中以源生成器织入到状态节点代码里的分布式 hooks 形式出现,
|
||||
%% 不再作为手写的公共基座类型单独暴露。
|
||||
|
||||
flowchart TB
|
||||
subgraph V[View 层]
|
||||
direction LR
|
||||
VH[ViewHost]
|
||||
SCC[StateChangeCommand]
|
||||
EC[ExecuteCommand]
|
||||
CR[CommandResult]
|
||||
end
|
||||
|
||||
subgraph S[State 层]
|
||||
direction TB
|
||||
subgraph SHBOX[StateHost]
|
||||
direction TB
|
||||
SH[唯一对外主机]
|
||||
|
||||
subgraph SU[上层]
|
||||
direction LR
|
||||
FCH[FrontCommandHost]
|
||||
end
|
||||
|
||||
subgraph SM[中层]
|
||||
direction LR
|
||||
AS[AppState]
|
||||
PS[PanelState]
|
||||
GOBS[生成器织入的分布式 Observer Hooks]
|
||||
end
|
||||
|
||||
subgraph SL[下层]
|
||||
direction LR
|
||||
MGR[ServiceManager]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
subgraph SV[Service 层]
|
||||
SLC[ServiceLoadCommand]
|
||||
SHOST[ServiceHost]
|
||||
SLAE[ServiceLoadAccomplishmentEvent]
|
||||
end
|
||||
|
||||
SH --> FCH
|
||||
SH --> AS
|
||||
SH --> MGR
|
||||
AS -->|聚合| PS
|
||||
PS -. 节点写入后触发 .-> GOBS
|
||||
MGR -->|管理| SHOST
|
||||
|
||||
VH -. 产生/投递 .-> SCC
|
||||
VH -. 产生/投递 .-> EC
|
||||
VH -. 命令送入 .-> SH
|
||||
SH -. 统一回传 .-> CR
|
||||
CR -. 返回给 .-> VH
|
||||
|
||||
FCH -. 接入/区分 .-> SCC
|
||||
FCH -. 接入/区分 .-> EC
|
||||
FCH -. 前侧结果组织 .-> CR
|
||||
|
||||
SH -. 下传执行请求 .-> MGR
|
||||
MGR -. 调度具体服务 .-> SHOST
|
||||
MGR -. 装载请求 .-> SLC
|
||||
SHOST -. 执行结果回流 .-> MGR
|
||||
SLC -. 初始化依据 .-> SHOST
|
||||
SHOST -. 装载完成产出 .-> SLAE
|
||||
|
||||
classDef host fill:#1d3557,stroke:#f1faee,color:#f1faee,stroke-width:2px;
|
||||
classDef state fill:#264653,stroke:#e9f5db,color:#f1faee;
|
||||
classDef view fill:#3d405b,stroke:#f2cc8f,color:#f1faee;
|
||||
classDef service fill:#4a4e69,stroke:#c9ada7,color:#f1faee;
|
||||
classDef generated fill:#6d597a,stroke:#ffcad4,color:#f1faee;
|
||||
|
||||
class SH host;
|
||||
class FCH,AS,PS,MGR state;
|
||||
class GOBS generated;
|
||||
class VH,SCC,EC,CR view;
|
||||
class SHOST,SLC,SLAE service;
|
||||
@@ -0,0 +1,531 @@
|
||||
# VSS 三层类型基座设计草案
|
||||
|
||||
## 目录
|
||||
|
||||
### 总览
|
||||
- [1. 设计目标](#1-设计目标)
|
||||
- [2. 整体结构概览](#2-整体结构概览)
|
||||
- [3. View 层类型基座](#3-view-层类型基座)
|
||||
- [4. State 层类型基座](#4-state-层类型基座)
|
||||
- [5. Service 层类型基座](#5-service-层类型基座)
|
||||
- [6. 三层之间的关系](#6-三层之间的关系)
|
||||
- [7. 当前建议保留的最小类型清单](#7-当前建议保留的最小类型清单)
|
||||
- [8. 后续扩展建议](#8-后续扩展建议)
|
||||
- [9. 结论](#9-结论)
|
||||
|
||||
### 已明确类型
|
||||
|
||||
#### View 层
|
||||
- [ViewHost](#321-viewhost)
|
||||
- [StateChangeCommand](#322-statechangecommand)
|
||||
- [ExecuteCommand](#323-executecommand)
|
||||
- [CommandResult](#324-commandresult)
|
||||
|
||||
#### State 层
|
||||
- [StateHost](#421-statehost)
|
||||
- [FrontCommandHost](#422-frontcommandhost)
|
||||
- [AppState](#423-appstate)
|
||||
- [PanelState](#424-panelstate)
|
||||
- [ServiceManager](#425-servicemanager)
|
||||
|
||||
#### Service 层
|
||||
- [ServiceLoadCommand](#521-serviceloadcommand)
|
||||
- [ServiceLoadAccomplishmentEvent](#522-serviceloadaccomplishmentevent)
|
||||
- [ServiceHost](#523-servicehost)
|
||||
|
||||
## 1. 设计目标
|
||||
|
||||
VSS 面向的是多宿主、多运行环境、多交互来源的系统,不应将整体架构绑定到单一 GUI 框架、单一线程模型或单一输入方式上。
|
||||
因此,VSS 的基础设计不以传统 ViewModel-View 对应关系为核心,而以统一状态中枢为运行时骨架。
|
||||
|
||||
在当前版本中,VSS 仍然保留 View、State、Service 三种职责域,但其运行时组织方式需要进一步明确:
|
||||
|
||||
- View 层负责承接外部交互来源
|
||||
- State 层负责作为唯一状态与调度中枢
|
||||
- Service 层负责承载系统能力与执行对象
|
||||
|
||||
其中,State 层不再被描述为若干并列对象的松散组合,而是对外统一表现为一个单例运行时主机:`StateHost`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 整体结构概览
|
||||
|
||||
VSS 当前建议的核心运行结构如下:
|
||||
|
||||
View / Electron / HMI / 外设 / 其他宿主
|
||||
↓
|
||||
StateHost(单例)
|
||||
├─ 上层:FrontCommandHost
|
||||
├─ 中层:AppState
|
||||
└─ 下层:ServiceManager
|
||||
↓
|
||||
Service / ServiceHost / 具体能力实例
|
||||
|
||||
这个结构表达的是两层含义:
|
||||
|
||||
1. 对外,整个 State 层只暴露一个统一中枢,即 `StateHost`。
|
||||
2. 对内,`StateHost` 全息展开为上中下三层:上层接命令,中层持状态,下层管服务。
|
||||
|
||||
其基本运行逻辑如下:
|
||||
|
||||
1. 外部宿主、界面、手柄、HMI 或进程外系统发出命令。
|
||||
2. 命令进入 `StateHost` 的上层,即 `FrontCommandHost`。
|
||||
3. `FrontCommandHost` 完成命令接入、规范化与前置转发。
|
||||
4. `StateHost` 的中层 `AppState` 作为全局状态抽象参与状态判断与状态更新。
|
||||
5. 当命令涉及能力执行时,`StateHost` 的下层 `ServiceManager` 负责服务管理与调度。
|
||||
6. 服务结果回流 `StateHost`,再统一同步到状态体系与观察者体系。
|
||||
|
||||
---
|
||||
|
||||
# 3. View 层类型基座
|
||||
|
||||
## 3.1 设计定位
|
||||
|
||||
View 层表示前侧宿主与交互来源,而不是具体控件集合。
|
||||
它只负责承接输入、形成命令,并与 `StateHost` 建立稳定的交互接口。
|
||||
|
||||
因此,View 层不承担系统中枢职责,也不直接管理服务。
|
||||
|
||||
---
|
||||
|
||||
## 3.2 View 层基础类型
|
||||
|
||||
### 3.2.1 `ViewHost`
|
||||
|
||||
#### 定义
|
||||
`ViewHost` 是 VSS 对前侧宿主的统一抽象,用于表示任何可以承接界面表现、输入来源或外部交互来源的宿主对象。
|
||||
|
||||
#### 职责
|
||||
- 承载某一类前端或外部交互上下文;
|
||||
- 接收宿主内产生的交互行为;
|
||||
- 将交互行为转为标准命令;
|
||||
- 将命令提交给 `StateHost`;
|
||||
- 接收由 `StateHost` 回传的 `CommandResult`;
|
||||
- 在必要时订阅状态变化并做宿主内部同步。
|
||||
|
||||
#### 说明
|
||||
`ViewHost` 不是具体 UI 控件,也不是具体页面对象。
|
||||
它更偏向交互宿主,例如图形界面宿主、HMI 宿主、外设宿主或远程宿主。
|
||||
|
||||
---
|
||||
|
||||
### 3.2.2 `StateChangeCommand`
|
||||
|
||||
#### 定义
|
||||
`StateChangeCommand` 用于表达对系统状态的直接修改请求。
|
||||
|
||||
#### 职责
|
||||
- 描述某项状态应当如何被修改;
|
||||
- 携带状态修改目标与修改参数;
|
||||
- 由 `StateHost` 接收并执行;
|
||||
- 执行后触发状态变更通知。
|
||||
|
||||
#### 说明
|
||||
这类命令本质上是确定性状态修改命令,适合用于审计、回放和状态同步。
|
||||
|
||||
对于高频、值类型占比高的状态节点,不应长期停留在 `object` 中继模型上。
|
||||
|
||||
更合理的做法是:
|
||||
|
||||
- `StateChangeCommand` 继续承担统一路由 envelope;
|
||||
- 具体节点更新优先落到类型化的 `StateValueCommand<TValue>`;
|
||||
- 由源生成器直接生成对应的 typed handler,避免在热点路径中反复装箱、拆箱和类型判断。
|
||||
|
||||
如果一次命令需要协调多个状态节点的原子更新,则不应继续复用单节点 `StateChangeCommand`,而应单独设计“向量命令”家族,将批量节点写入、冲突检测、原子性与回滚语义显式建模。
|
||||
|
||||
---
|
||||
|
||||
### 3.2.3 `ExecuteCommand`
|
||||
|
||||
#### 定义
|
||||
`ExecuteCommand` 用于表达需要进入执行流程的命令请求。
|
||||
|
||||
#### 职责
|
||||
- 表达需要被执行而不是被直接改写为状态的动作;
|
||||
- 以当前状态为上下文触发流程、能力调用或服务调度;
|
||||
- 由 `StateHost` 判断是否需要下传到 `ServiceManager`;
|
||||
- 在执行结束后对应产生一个 `CommandResult`。
|
||||
|
||||
#### 说明
|
||||
这类命令更接近执行请求,而不是单一字段修改。
|
||||
|
||||
---
|
||||
|
||||
### 3.2.4 `CommandResult`
|
||||
|
||||
#### 定义
|
||||
`CommandResult` 用于表达某次前侧命令处理完成后的统一结果通知。
|
||||
|
||||
#### 职责
|
||||
- 作为 `StateHost` 回传给 `ViewHost` 的统一结果对象;
|
||||
- 通知某次命令是否已经处理完成;
|
||||
- 描述执行成功、失败、拒绝或中止等结果状态;
|
||||
- 在必要时携带消息、结果数据或关联标识。
|
||||
|
||||
#### 说明
|
||||
如果没有 `CommandResult`,View 只能看到状态变化,却无法明确知道某次命令是否已经结束。
|
||||
因此,`CommandResult` 应被视为前侧命令协议中必需的回向对象。
|
||||
|
||||
---
|
||||
|
||||
## 3.3 View 层总结
|
||||
|
||||
View 层的基础作用只有三点:
|
||||
|
||||
1. 提供统一的前侧宿主抽象;
|
||||
2. 将复杂交互来源统一转换为命令;
|
||||
3. 将命令稳定送入 `StateHost`。
|
||||
|
||||
当前基础类型包括:
|
||||
|
||||
- `ViewHost`
|
||||
- `StateChangeCommand`
|
||||
- `ExecuteCommand`
|
||||
- `CommandResult`
|
||||
|
||||
---
|
||||
|
||||
# 4. State 层类型基座
|
||||
|
||||
## 4.1 设计定位
|
||||
|
||||
State 层是 VSS 的唯一状态与调度中枢。
|
||||
它对外统一表现为 `StateHost` 单例,而不是多个并列对象分别对外承担中枢职责。
|
||||
|
||||
`StateHost` 的内部结构分为三层:
|
||||
|
||||
- 上层:`FrontCommandHost`
|
||||
- 中层:`AppState`
|
||||
- 下层:`ServiceManager`
|
||||
|
||||
因此,State 层既是统一入口,也是统一协调边界。
|
||||
|
||||
---
|
||||
|
||||
## 4.2 State 层基础类型
|
||||
|
||||
### 4.2.1 `StateHost`
|
||||
|
||||
#### 定义
|
||||
`StateHost` 是 VSS 对外暴露的唯一状态主机,建议以单例形式存在。
|
||||
|
||||
#### 职责
|
||||
- 作为整个 State 层的唯一外观;
|
||||
- 对外承接来自 `ViewHost` 或其他入口的命令;
|
||||
- 对内组织 `FrontCommandHost`、`AppState` 与 `ServiceManager`;
|
||||
- 统一管理状态判断、状态同步、服务调度与结果回流;
|
||||
- 作为状态变化通知与观察者体系的汇聚点。
|
||||
|
||||
#### 说明
|
||||
`StateHost` 是运行时主机概念,不等价于某一个状态对象。
|
||||
它的价值在于为整个 State 层提供唯一边界与唯一所有权。
|
||||
|
||||
---
|
||||
|
||||
### 4.2.2 `FrontCommandHost`
|
||||
|
||||
#### 定义
|
||||
`FrontCommandHost` 是 `StateHost` 的上层结构,用于承接前侧命令输入。
|
||||
|
||||
#### 职责
|
||||
- 接收来自不同 `ViewHost` 的命令;
|
||||
- 统一前侧命令入口;
|
||||
- 区分 `StateChangeCommand` 与 `ExecuteCommand` 两类命令;
|
||||
- 对命令进行初步规范化;
|
||||
- 执行必要的前置校验;
|
||||
- 将命令转交给 `StateHost` 的中层与下层进行后续处理;
|
||||
- 参与将处理结果组织为 `CommandResult` 回传前侧。
|
||||
|
||||
#### 说明
|
||||
`FrontCommandHost` 是 State 的上边界,不再被视为独立于 State 层之外的中介对象。
|
||||
|
||||
---
|
||||
|
||||
- 统一向前侧回传 `CommandResult`;
|
||||
### 4.2.3 `AppState`
|
||||
|
||||
#### 定义
|
||||
`AppState` 是 `StateHost` 中层所持有的全局状态抽象,是 VSS 的唯一可信状态树根。
|
||||
|
||||
#### 职责
|
||||
- 聚合多个 `PanelState`;
|
||||
- 保存全局运行时配置;
|
||||
- 保存当前会话状态;
|
||||
- 保存系统共享上下文;
|
||||
- 作为状态判断和状态变更的核心依据;
|
||||
- 为状态广播与观察提供基础数据。
|
||||
|
||||
#### 说明
|
||||
`AppState` 代表状态本体,而不是状态宿主。
|
||||
它由 `StateHost` 持有和管理。
|
||||
|
||||
---
|
||||
|
||||
### 4.2.4 `PanelState`
|
||||
|
||||
#### 定义
|
||||
`PanelState` 是面板级状态单元,用于表示某个功能域的局部状态。
|
||||
|
||||
#### 职责
|
||||
- 保存局部运行状态;
|
||||
- 作为 `AppState` 的子状态节点;
|
||||
- 为不同功能模块提供独立状态边界;
|
||||
- 支持局部状态的独立演进与扩展。
|
||||
|
||||
#### 说明
|
||||
`PanelState` 使状态结构具备模块化能力,避免所有字段直接堆叠在 `AppState` 根节点上。
|
||||
|
||||
如果沿用状态机术语,更准确的说法应当是:VSS 当前更接近“向量状态存储 + 受控变更路由”,而不是经典有限状态机。
|
||||
|
||||
这里的典型映射关系是:
|
||||
|
||||
- 状态节点值对应状态向量的各分量;
|
||||
- `CanApply` 更接近 transition guard;
|
||||
- 实际赋值是状态向量写入;
|
||||
- `BroadcastChange` / observer hook 更接近 transition effect;
|
||||
- route 或 command path 只是进入某条受控变更管线的路由键,不等于经典状态机中的单一状态迁移边。
|
||||
|
||||
---
|
||||
|
||||
当前版本仍然保留 observer 语义,但不再保留 `OnStateChanged` 与 `StateChangeObserver` 这两个手写的公开类型。
|
||||
|
||||
在 `StgSharp.UserInterface` 中,状态写入后的前置校验、实际赋值与后置 observer 联动,统一改为由源生成器围绕具体状态节点分布式生成。
|
||||
|
||||
在传统 MVVM 中,observer 往往同时承担:
|
||||
|
||||
- 内部逻辑回调
|
||||
- VM 内部广播变化
|
||||
- 通知 View 重绘
|
||||
- 通知 Data / 业务执行
|
||||
|
||||
而在 VSS 中,这四项职责被拆开后分别落到不同位置:
|
||||
|
||||
- 内部逻辑回调:由 Service 或具体业务实现承担
|
||||
- VM 内部广播变化:由源生成器织入的轻量 observer hook 承担
|
||||
- 通知 View 重绘:由 command/result 与同步符号链路承担
|
||||
- 通知 Data / 业务执行:由 command/result 与 service 调度承担
|
||||
|
||||
因此,observer 在 VSS 中并没有消失,只是从一个中心化、手写的公共类型,变成了围绕各状态节点分布实现的生成代码职责。
|
||||
|
||||
---
|
||||
|
||||
### 4.2.5 `ServiceManager`
|
||||
|
||||
#### 定义
|
||||
`ServiceManager` 是 `StateHost` 的下层结构,用于管理服务体系。
|
||||
|
||||
#### 职责
|
||||
- 管理服务注册、装载、卸载与生命周期;
|
||||
- 维护服务实例与执行上下文;
|
||||
- 接收 `StateHost` 下传的能力请求;
|
||||
- 将服务执行结果回流给 `StateHost`;
|
||||
- 作为 State 层通往具体能力层的下边界。
|
||||
|
||||
#### 说明
|
||||
`ServiceManager` 虽然位于 `StateHost` 内部下层,但它管理的仍然是能力体系,而不是状态本体。
|
||||
这一点必须与 `AppState` 严格区分。
|
||||
|
||||
---
|
||||
|
||||
## 4.3 State 层总结
|
||||
|
||||
State 层应被理解为一个统一主机,而不是若干同级对象的松散拼装。
|
||||
`StateHost` 对外是单例中枢,对内全息展开为上中下三层:
|
||||
|
||||
- 上层负责命令接入
|
||||
- 中层负责状态抽象
|
||||
- 下层负责服务管理
|
||||
|
||||
同时,State 对前侧的完整协议应当至少包含:
|
||||
|
||||
- `StateChangeCommand`
|
||||
- `ExecuteCommand`
|
||||
- `CommandResult`
|
||||
|
||||
当前基础类型包括:
|
||||
|
||||
- `StateHost`
|
||||
- `FrontCommandHost`
|
||||
- `AppState`
|
||||
- `PanelState`
|
||||
- `ServiceManager`
|
||||
|
||||
此外,还存在由源生成器分布式织入的 observer 机制,用于承担轻量的状态节点内部广播,但它不再作为单独的公开基座类型出现。
|
||||
|
||||
---
|
||||
|
||||
# 5. Service 层类型基座
|
||||
|
||||
## 5.1 设计定位
|
||||
|
||||
Service 层是系统能力的承载层。
|
||||
这里的重点不再是把 `ServiceManager` 视为一个独立的外部中枢,而是将其视为 `StateHost` 下层所管理的能力入口。
|
||||
|
||||
因此,Service 层更关注的是被管理的服务对象、服务装载命令和服务完成事件。
|
||||
|
||||
---
|
||||
|
||||
## 5.2 Service 层基础类型
|
||||
|
||||
### 5.2.1 `ServiceLoadCommand`
|
||||
|
||||
#### 定义
|
||||
`ServiceLoadCommand` 用于表达服务装载请求。
|
||||
|
||||
#### 职责
|
||||
- 描述需要装载的服务;
|
||||
- 描述装载目标与装载参数;
|
||||
- 由 `StateHost` 下传给 `ServiceManager`;
|
||||
- 驱动服务实例创建、初始化或激活。
|
||||
|
||||
---
|
||||
|
||||
### 5.2.2 `ServiceLoadAccomplishmentEvent`
|
||||
|
||||
#### 定义
|
||||
`ServiceLoadAccomplishmentEvent` 用于表达服务装载动作完成后的结果通知。
|
||||
|
||||
#### 职责
|
||||
- 通知服务是否完成装载;
|
||||
- 通知装载是否成功;
|
||||
- 承载失败信息或附加上下文;
|
||||
- 支持结果回流到 `StateHost` 并同步状态。
|
||||
|
||||
---
|
||||
|
||||
### 5.2.3 `ServiceHost`
|
||||
|
||||
#### 定义
|
||||
`ServiceHost` 用于表达某一类服务实例的直接宿主或执行载体。
|
||||
|
||||
#### 职责
|
||||
- 承接某类服务的运行实例;
|
||||
- 为服务提供执行上下文;
|
||||
- 支持未来多宿主、多环境的服务承载模式;
|
||||
- 作为 `ServiceManager` 管理的底层运行单元之一。
|
||||
|
||||
---
|
||||
|
||||
## 5.3 Service 层总结
|
||||
|
||||
Service 层负责真正承载系统能力。
|
||||
`ServiceManager` 属于 `StateHost` 的下层管理结构,而 `ServiceHost` 与具体服务实例才构成能力层主体。
|
||||
|
||||
当前基础类型包括:
|
||||
|
||||
- `ServiceLoadCommand`
|
||||
- `ServiceLoadAccomplishmentEvent`
|
||||
- `ServiceHost`
|
||||
|
||||
---
|
||||
|
||||
# 6. 三层之间的关系
|
||||
|
||||
## 6.1 View → State
|
||||
|
||||
View 层不直接管理服务,也不直接改写系统运行结构。
|
||||
其职责是通过命令将外部交互送入 `StateHost`。
|
||||
|
||||
即:
|
||||
|
||||
- `ViewHost` 产生命令;
|
||||
- 命令分为 `StateChangeCommand` 与 `ExecuteCommand`;
|
||||
- 命令进入 `StateHost`;
|
||||
- `StateHost` 的上层 `FrontCommandHost` 负责接入和前置处理;
|
||||
- 命令处理完成后由 `StateHost` 向 `ViewHost` 回传 `CommandResult`。
|
||||
|
||||
---
|
||||
|
||||
## 6.2 State 内部三层关系
|
||||
|
||||
`StateHost` 的内部关系如下:
|
||||
|
||||
- `FrontCommandHost` 负责接命令;
|
||||
- `AppState` 负责持状态;
|
||||
- `ServiceManager` 负责管服务。
|
||||
|
||||
这三者不是并列对外暴露,而是共同组成一个统一的 State 运行时主机。
|
||||
|
||||
---
|
||||
|
||||
## 6.3 State → Service
|
||||
|
||||
当命令涉及能力执行时,`StateHost` 通过下层 `ServiceManager` 调度具体服务对象。
|
||||
服务执行结果再统一回流 `StateHost`,由其决定状态更新与事件广播。
|
||||
|
||||
---
|
||||
|
||||
# 7. 当前建议保留的最小类型清单
|
||||
|
||||
## View 层
|
||||
- `ViewHost`
|
||||
- `StateChangeCommand`
|
||||
- `ExecuteCommand`
|
||||
- `CommandResult`
|
||||
|
||||
## State 层
|
||||
- `StateHost`
|
||||
- `FrontCommandHost`
|
||||
- `AppState`
|
||||
- `PanelState`
|
||||
- `ServiceManager`
|
||||
|
||||
## Service 层
|
||||
- `ServiceLoadCommand`
|
||||
- `ServiceLoadAccomplishmentEvent`
|
||||
- `ServiceHost`
|
||||
|
||||
---
|
||||
|
||||
# 8. 后续扩展建议
|
||||
|
||||
在当前基础类型稳定后,建议后续按以下顺序扩展:
|
||||
|
||||
## 第一阶段:固定唯一中枢边界
|
||||
- 明确 `StateHost` 的单例角色;
|
||||
- 明确 `FrontCommandHost` 是 `StateHost` 的上层;
|
||||
- 明确 `AppState` 是 `StateHost` 的中层状态抽象;
|
||||
- 明确 `ServiceManager` 是 `StateHost` 的下层服务管理结构。
|
||||
|
||||
## 第二阶段:扩展具体子类型
|
||||
|
||||
### View 侧
|
||||
- 图形界面宿主
|
||||
- HMI 宿主
|
||||
- 外设宿主
|
||||
- 远程宿主
|
||||
|
||||
### State 侧
|
||||
- 更多 `PanelState` 子类型
|
||||
- 更明确的状态变化事件类型
|
||||
- 更完整的观察者与同步机制
|
||||
|
||||
### Service 侧
|
||||
- 具体服务实例
|
||||
- 具体 `ServiceHost` 子类型
|
||||
- 更细化的服务装载与能力描述体系
|
||||
|
||||
## 第三阶段:补充运行时机制
|
||||
- 命令上下文;
|
||||
- 状态快照;
|
||||
- 事件总线;
|
||||
- 服务能力描述;
|
||||
- 审计与回放;
|
||||
- 服务结果同步协议。
|
||||
|
||||
---
|
||||
|
||||
# 9. 结论
|
||||
|
||||
VSS 当前不应先从具体界面技术切入,而应先定义稳定的运行时类型基座。
|
||||
在这一前提下,State 层需要被明确为唯一统一中枢,而不是若干对象之间的松散协作。
|
||||
|
||||
因此,当前版本更合适的表述是:
|
||||
|
||||
- View 层负责交互来源
|
||||
- State 层对外统一表现为 `StateHost` 单例
|
||||
- `StateHost` 对内全息展开为 `FrontCommandHost`、`AppState`、`ServiceManager` 三层
|
||||
- 前侧命令协议由 `StateChangeCommand`、`ExecuteCommand`、`CommandResult` 共同构成
|
||||
- Service 层负责承载被 `ServiceManager` 管理的具体能力对象
|
||||
|
||||
这一定义能更清楚地表达所有权、调度边界与运行时结构。
|
||||
@@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.UnserInterface.View
|
||||
{
|
||||
internal class PresenterDescription
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/28.2.5 Chrome/138.0.7204.251 Electron/37.6.1 Safari/537.36" version="28.2.5">
|
||||
<diagram name="第 1 页" id="cbTJhW8NyRPDnIMFj9ZQ">
|
||||
<mxGraphModel dx="982" dy="90" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#797979" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-4" value="视图托管接口VH<br>ViewHost" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=bottom;gradientColor=none;fillColor=#3399FF;fontColor=#000000;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="-210" y="40" width="430" height="200" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-36" value="状态宿主 StateHost" style="verticalLabelPosition=middle;verticalAlign=top;html=1;shape=mxgraph.basic.rect;fillColor2=none;strokeWidth=1;size=0;indent=0;fillColor=#3399FF;labelPosition=center;align=center;strokeColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="-420" y="410" width="440" height="340" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-37" value="呈现器VP<br>View Presenter" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="-200" y="50" width="210" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-38" value="视图描述VD<br>View Descriptor" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="-200" y="120" width="210" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="oCZHK8Ia19RPNdtHcHY8-39" value="用户输入 I<br>Input" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="50" width="190" height="140" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#66FF66;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-4" target="25TvGm5YAioSoPTIv9tO-10">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="150" y="275" />
|
||||
<mxPoint x="60" y="275" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#66FF66;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-4" target="25TvGm5YAioSoPTIv9tO-11">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="150" y="275" />
|
||||
<mxPoint x="227" y="275" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-6" value="<h1 style="margin-top: 0px;">视图 View</h1><p>视图的类型基座是VieHost,一个用户界面应当具有唯一对应的ViewHost。<br>View形式多样,除了图形化操作系统窗体,手柄等HMI也属于视图。<br>跨进程图形界面通过State进程内的Host进行交互</p><p>视图本身不处理任何业务逻辑,只将用户的操作转为Command下发给State</p><p>在图形化界面中:<br>呈现器VP指OpenGL等底层渲染程序,skia等中层加速库,甚至electron框架<br><span style="background-color: transparent;">视图描述VD指XAML,原生代码,CSS等描述界面上控件布局的代码</span></p><p>一个程序可以有很多个交互视图,且可以对视图的实现和布局进行丰富地修改<br>只要使用相同的命令,就可以和State进行交互。且设计良好的VSS程序中,<br>视图下线不应当影响State和Service的运行</p>" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;fontColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="247" y="40" width="460" height="250" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.481;entryY=1.021;entryDx=0;entryDy=0;strokeColor=#FFFF66;entryPerimeter=0;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-7" target="25TvGm5YAioSoPTIv9tO-16">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="-110" y="385" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-7" target="25TvGm5YAioSoPTIv9tO-33">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="-530" y="700" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="50" y="460" />
|
||||
<mxPoint x="50" y="720" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-57" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeColor=#FFFF33;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-7" target="25TvGm5YAioSoPTIv9tO-53">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="-310" y="390" />
|
||||
<mxPoint x="-310" y="390" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-7" value="前端命令调度器 CommandHandler" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="-380" y="441" width="360" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#66FF66;entryX=0.946;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-10" target="25TvGm5YAioSoPTIv9tO-7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="-40" y="440" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="60" y="390" />
|
||||
<mxPoint x="-40" y="390" />
|
||||
<mxPoint x="-40" y="440" />
|
||||
<mxPoint x="-39" y="440" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-10" value="StateChangeCommand" style="rounded=1;whiteSpace=wrap;html=1;fontColor=#000000;strokeColor=none;fillColor=#66FF66;" vertex="1" parent="1">
|
||||
<mxGeometry x="-13" y="310" width="146" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-15" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.945;entryY=0.011;entryDx=0;entryDy=0;strokeColor=#33FF33;entryPerimeter=0;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-11" target="25TvGm5YAioSoPTIv9tO-7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="230" y="390" />
|
||||
<mxPoint x="-40" y="390" />
|
||||
<mxPoint x="-40" y="440" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-11" value="CallOperationCommand" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#66FF66;fontColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="154" y="310" width="146" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.25;entryY=1;entryDx=0;entryDy=0;strokeColor=#FFFF33;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-16" target="25TvGm5YAioSoPTIv9tO-4">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-16" value="CommandResult" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFF33;fontColor=#000000;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="-180" y="310" width="146" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-19" value="<h1 style="margin-top: 0px;">前端命令 FrontCommand</h1><p><span style="background-color: transparent;">View和State通过前端命令进行交互<br>进程内通信可以直接传递对象<br>跨进程交互可以传递序列化后的json数据包<br></span></p><p>State中响应command的结果通过CommandResult<br>数据包返回给View</p><p>在需要时,View通过前端命令获取State的快照进行图形化展示</p>" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;align=left;fontColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="-790" y="250" width="290" height="190" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-37" value="<font style="font-size: 12px; color: rgb(247, 247, 247);">程序状态集 AppState</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#000099;dashed=1;dashPattern=12 12;verticalAlign=top;fontSize=9;" vertex="1" parent="1">
|
||||
<mxGeometry x="-410" y="520" width="420" height="170" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-22" value="<font style="color: rgb(0, 0, 0);">局部状态 PS<br>PanelState0</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#66FFFF;fontColor=#ffffff;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="-400" y="540" width="90" height="140" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-23" value="<font style="color: rgb(0, 0, 0);">PanelState1</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#66FFFF;fontColor=#ffffff;strokeColor=none;verticalAlign=top;" vertex="1" parent="1">
|
||||
<mxGeometry x="-300" y="540" width="300" height="140" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-52" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontColor=#000000;labelBackgroundColor=none;strokeColor=#000000;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-33">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="-380" y="460" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="-450" y="720" />
|
||||
<mxPoint x="-450" y="460" />
|
||||
<mxPoint x="-380" y="460" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-70" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#66FF66;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-33" target="25TvGm5YAioSoPTIv9tO-65">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="-160" y="775" />
|
||||
<mxPoint x="-126" y="775" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-86" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.657;entryY=0.001;entryDx=0;entryDy=0;entryPerimeter=0;strokeColor=#66FF66;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-33" target="25TvGm5YAioSoPTIv9tO-83">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint y="740" />
|
||||
<mxPoint y="870" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-88" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;strokeColor=#66FF66;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-33" target="25TvGm5YAioSoPTIv9tO-79">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="-390" y="740" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-33" value="后端命令调度器 CommandHandler" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="-370" y="700" width="360" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-24" value="状态节点0<br>StateNode0" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=#000000;fillColor=#FFE599;fontColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="-221" y="560" width="71" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-25" value="状态节点1" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=#000000;fillColor=#FFE599;fontColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="-150" y="560" width="71" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-21" value="<font style="color: rgb(0, 0, 0);">状态变更管线</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#000099;dashed=1;dashPattern=12 12;verticalAlign=top;fontSize=9;" vertex="1" parent="1">
|
||||
<mxGeometry x="-294" y="610" width="284" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-41" target="25TvGm5YAioSoPTIv9tO-26">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="-80" y="650" />
|
||||
<mxPoint x="-80" y="650" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-41" value="<font style="font-size: 9px;">可更新检查<br>UpdateCheck</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" vertex="1" parent="1">
|
||||
<mxGeometry x="-80" y="635" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-43" value="" style="group" vertex="1" connectable="0" parent="1">
|
||||
<mxGeometry x="-210" y="630" width="121" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-26" value="状态节点2" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=#000000;fillColor=#FFE599;fontColor=#000000;" vertex="1" parent="25TvGm5YAioSoPTIv9tO-43">
|
||||
<mxGeometry x="40" width="71" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-42" value="<font style="font-size: 9px;">更新</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" vertex="1" parent="25TvGm5YAioSoPTIv9tO-43">
|
||||
<mxGeometry x="10" width="30" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-48" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.25;entryY=1;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-45" target="25TvGm5YAioSoPTIv9tO-7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="-256" y="600" />
|
||||
<mxPoint x="-290" y="600" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-49" value="状态更新回调" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];labelBackgroundColor=none;fontColor=#000000;" vertex="1" connectable="0" parent="25TvGm5YAioSoPTIv9tO-48">
|
||||
<mxGeometry x="0.6704" y="1" relative="1" as="geometry">
|
||||
<mxPoint y="-2" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-45" value="<font style="font-size: 9px;">状态更新观测器<br>UpdateObserver</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0050ef;fontColor=#ffffff;strokeColor=#001DBC;" vertex="1" parent="1">
|
||||
<mxGeometry x="-291" y="625" width="70" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-47" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-42" target="25TvGm5YAioSoPTIv9tO-45">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="-200" y="650" />
|
||||
<mxPoint x="-200" y="650" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-50" value="<h1 style="margin-top: 0px;">状态 State</h1><p>State是VSS架构的核心,用于同步多种不同的视图输入并仲裁其操作<br></p><p>状态层通过状态宿主SH托管,SH应当为单例模式且程序生命周期内唯一,<br>具有多个SH的程序将变为分布式程序,不是VSS架构支持的目标。</p><p>整个程序的状态存放在程序状态集AS中,并进一步按照可视化界面和功能组<br>分为不同的局部状态PS,一个状态节点一般为一个属性,PS整体形成向量状态机</p><p>前端命令通过状态更新管线进行,状态更新管线使用源生成器自动化构建,不需要手写<br>执行管线会将执行结果返回到前端中。</p><p>复杂的,涉及到重型任务的操作将提交给后端命令调度器执行,后端命令调度器将根据<br>当前状态和命令要求,针对每一个Service录制一组指令流下发,并在执行过程中进行同步</p>" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;fontColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="60" y="475" width="520" height="240" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-51" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeColor=#000000;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-7" target="25TvGm5YAioSoPTIv9tO-41">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="70" y="850" as="targetPoint" />
|
||||
<mxPoint x="60" y="590" as="sourcePoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="50" y="460" />
|
||||
<mxPoint x="50" y="650" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-58" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;strokeColor=#66FF66;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-53" target="25TvGm5YAioSoPTIv9tO-7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="-290" y="290" />
|
||||
<mxPoint x="-290" y="290" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-53" value="ViewHost 1" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=middle;gradientColor=none;fillColor=#3399FF;fontColor=#000000;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="-400" y="210" width="180" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-54" value="ViewHost 2" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=middle;gradientColor=none;fillColor=#3399FF;fontColor=#000000;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="-577" y="40" width="167" height="200" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-55" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#FFFF33;exitX=0;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="-355.34000000000003" y="390" as="sourcePoint" />
|
||||
<mxPoint x="-490" y="240" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="-380" y="330" />
|
||||
<mxPoint x="-490" y="330" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-59" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.056;entryY=0.015;entryDx=0;entryDy=0;entryPerimeter=0;strokeColor=#66FF66;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-54" target="25TvGm5YAioSoPTIv9tO-7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="-470" y="310" />
|
||||
<mxPoint x="-360" y="310" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-64" value="" style="group;fontColor=#000000;" vertex="1" connectable="0" parent="1">
|
||||
<mxGeometry x="-265" y="850" width="160" height="200" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-62" value="" style="group" vertex="1" connectable="0" parent="25TvGm5YAioSoPTIv9tO-64">
|
||||
<mxGeometry y="17.391304347826086" width="160" height="182.6086956521739" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-60" value="服务宿主 ServiceHost" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#66B2FF;" vertex="1" parent="25TvGm5YAioSoPTIv9tO-62">
|
||||
<mxGeometry width="160" height="23.18840579710145" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-61" value="服务功能 ServiceLogic" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0066CC;fontColor=#ffffff;strokeColor=#F5F5F5;" vertex="1" parent="25TvGm5YAioSoPTIv9tO-62">
|
||||
<mxGeometry y="23.18840579710145" width="160" height="133.33333333333331" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-63" value="服务 Service" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="25TvGm5YAioSoPTIv9tO-62">
|
||||
<mxGeometry y="156.52173913043478" width="160" height="26.08695652173913" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-65" value="ServiceCommand" style="rounded=1;whiteSpace=wrap;html=1;fontColor=#000000;strokeColor=none;fillColor=#66FF66;" vertex="1" parent="1">
|
||||
<mxGeometry x="-180" y="810" width="107" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-71" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#FFFF33;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-66">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="-200" y="740" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="-236" y="775" />
|
||||
<mxPoint x="-200" y="775" />
|
||||
<mxPoint x="-200" y="740" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-66" value="CommandResult" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#FFFF33;fontColor=#000000;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="-291" y="810" width="109" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-68" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;strokeColor=#FFFF33;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-60" target="25TvGm5YAioSoPTIv9tO-66">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="-190" y="850" />
|
||||
<mxPoint x="-236" y="850" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-69" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#66FF66;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-65">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="-170" y="870" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="-126" y="850" />
|
||||
<mxPoint x="-170" y="850" />
|
||||
<mxPoint x="-170" y="870" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-78" value="" style="group" vertex="1" connectable="0" parent="1">
|
||||
<mxGeometry x="-420" y="870" width="120" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-79" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0066CC;fontColor=#ffffff;strokeColor=#F5F5F5;" vertex="1" parent="25TvGm5YAioSoPTIv9tO-78">
|
||||
<mxGeometry width="119.99999999999999" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-80" value="服务 1<br>Service 1" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="25TvGm5YAioSoPTIv9tO-78">
|
||||
<mxGeometry y="75" width="119.99999999999999" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-82" value="" style="group" vertex="1" connectable="0" parent="1">
|
||||
<mxGeometry x="-79" y="870" width="120" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-83" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0066CC;fontColor=#ffffff;strokeColor=#F5F5F5;" vertex="1" parent="25TvGm5YAioSoPTIv9tO-82">
|
||||
<mxGeometry width="119.99999999999999" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-84" value="服务 0<br>Service 0" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="25TvGm5YAioSoPTIv9tO-82">
|
||||
<mxGeometry y="16.666666666666664" width="119.99999999999999" height="6.666666666666666" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-85" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#FFFF33;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-83">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="-20" y="740" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="-20" y="740" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-87" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#FFFF33;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-79">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="-360" y="740" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="-360" y="740" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-89" value="<h1 style="margin-top: 0px;">后端命令 ServiceCommand</h1><p><span style="background-color: transparent;">State通过后端命令控制Service进行交互<br>进程内通信可以直接传递对象<br>跨进程交互可以传递复杂的配置数据包<br></span></p><p>Service按照命令序列执行操作</p><p>有专门的Fence命令用以控制多个服务之间的同步</p>" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;align=left;fontColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="-780" y="740" width="330" height="155" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-90" value="跨进程界面<br>ExternalDisplaySurface" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=#FFFF99;fillColor=#FFFF99;fontColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="-400" y="40" width="180" height="130" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-92" value="" style="shape=flexArrow;endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;endWidth=9.056603773584905;endSize=2.0638888888888887;startWidth=9.865229110512129;startSize=1.2074074074074073;width=2.7777777777777777;" edge="1" parent="1" source="25TvGm5YAioSoPTIv9tO-53" target="25TvGm5YAioSoPTIv9tO-90">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="-360" y="290" as="sourcePoint" />
|
||||
<mxPoint x="-260" y="190" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-93" value="json数据包" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];labelBackgroundColor=none;fontColor=#000000;" vertex="1" connectable="0" parent="25TvGm5YAioSoPTIv9tO-92">
|
||||
<mxGeometry x="-0.2988" relative="1" as="geometry">
|
||||
<mxPoint y="-6" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-94" value="跨进程服务<br>ExternalService" style="rounded=0;whiteSpace=wrap;html=1;strokeColor=#FFFF99;fillColor=#FFFF99;fontColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="-79" y="940" width="120" height="130" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-95" value="" style="shape=flexArrow;endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;endWidth=6.633249791144528;endSize=2.0548872180451125;startWidth=6.633249791144528;startSize=2.481203007518797;width=2.7777777777777777;" edge="1" parent="1" target="25TvGm5YAioSoPTIv9tO-94" source="25TvGm5YAioSoPTIv9tO-83">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="10" y="1100" as="sourcePoint" />
|
||||
<mxPoint x="60" y="1080" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-96" value="数据包" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];labelBackgroundColor=none;fontColor=#000000;" vertex="1" connectable="0" parent="25TvGm5YAioSoPTIv9tO-95">
|
||||
<mxGeometry x="-0.2988" relative="1" as="geometry">
|
||||
<mxPoint x="39" y="-1" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="25TvGm5YAioSoPTIv9tO-97" value="<h1 style="margin-top: 0px;">服务 Service</h1><p>VSS中重型的任务应当抽象为服务<br></p><p>诸如HPC数学计算,连续的数控操作等都应当各自抽象为一个服务,<br>甚至集群计算中的计算节点也可以抽象为集群管理软件的服务实例<br>一个VSS程序可以有很多个Service。</p><p>一个良好的设计的VSS架构程序中,一个服务的失效可能导致程序功能出错,<br>但是不可以影响其他Service实例的工作。</p>" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;fontColor=#000000;" vertex="1" parent="1">
|
||||
<mxGeometry x="70" y="850" width="520" height="240" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
Reference in New Issue
Block a user