1
0
mirror of https://github.com/soarqin/DSP_Mods.git synced 2025-12-08 22:13:30 +08:00

WIP: LabOpt

This commit is contained in:
2023-07-26 21:49:41 +08:00
parent 4883166faf
commit 17d80ea199
10 changed files with 441 additions and 1 deletions

4
.gitignore vendored
View File

@@ -5,5 +5,7 @@
bin/
obj/
# packaged zips
# packaged files
/*/package/*.zip
/*/package/patchers/
/*/package/plugins/

View File

@@ -18,6 +18,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OverclockEverything", "Over
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MechaDronesTweaks", "MechaDronesTweaks\MechaDronesTweaks.csproj", "{15B8BC2E-93E0-4454-8F8F-BF1FA8DC90F4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LabOpt", "LabOpt\LabOpt.csproj", "{DD76B77C-68D8-4A4C-B98E-CBEECEDC49FB}"
ProjectSection(ProjectDependencies) = postProject
{F2D5EBE8-7D9D-4572-9A3E-4DD1114FD99F} = {F2D5EBE8-7D9D-4572-9A3E-4DD1114FD99F}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LabOptPreloader", "LabOptPreloader\LabOptPreloader.csproj", "{F2D5EBE8-7D9D-4572-9A3E-4DD1114FD99F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -60,5 +67,13 @@ Global
{15B8BC2E-93E0-4454-8F8F-BF1FA8DC90F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15B8BC2E-93E0-4454-8F8F-BF1FA8DC90F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15B8BC2E-93E0-4454-8F8F-BF1FA8DC90F4}.Release|Any CPU.Build.0 = Release|Any CPU
{DD76B77C-68D8-4A4C-B98E-CBEECEDC49FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD76B77C-68D8-4A4C-B98E-CBEECEDC49FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD76B77C-68D8-4A4C-B98E-CBEECEDC49FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD76B77C-68D8-4A4C-B98E-CBEECEDC49FB}.Release|Any CPU.Build.0 = Release|Any CPU
{F2D5EBE8-7D9D-4572-9A3E-4DD1114FD99F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2D5EBE8-7D9D-4572-9A3E-4DD1114FD99F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2D5EBE8-7D9D-4572-9A3E-4DD1114FD99F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2D5EBE8-7D9D-4572-9A3E-4DD1114FD99F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

309
LabOpt/LabOpt.cs Normal file
View File

@@ -0,0 +1,309 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using BepInEx;
using HarmonyLib;
namespace LabOpt;
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
public class LabOptPatch : BaseUnityPlugin
{
public new static readonly BepInEx.Logging.ManualLogSource Logger =
BepInEx.Logging.Logger.CreateLogSource(PluginInfo.PLUGIN_NAME);
private void Awake()
{
Harmony.CreateAndPatchAll(typeof(LabOptPatch));
}
// Remove use of LabComponent.UpdateOutputToNext() for single-thread mode
[HarmonyTranspiler]
[HarmonyPatch(typeof(PlanetFactory), "GameTick")]
private static IEnumerable<CodeInstruction> RemoveLabUpdateOutputToNextForSingleThread(
IEnumerable<CodeInstruction> instructions,
ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Callvirt, AccessTools.Method(typeof(FactorySystem), nameof(FactorySystem.GameTickLabOutputToNext), new[] { typeof(long), typeof(bool) })))
.Advance(-4)
.RemoveInstructions(5);
return matcher.InstructionEnumeration();
}
// Remove use of LabComponent.UpdateOutputToNext() for multi-threads mode
[HarmonyTranspiler]
[HarmonyPatch(typeof(WorkerThreadExecutor), nameof(WorkerThreadExecutor.ComputerThread))]
private static IEnumerable<CodeInstruction> RemoveLabUpdateOutputToNextForMultiThreads(
IEnumerable<CodeInstruction> instructions,
ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false, new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(WorkerThreadExecutor), nameof(WorkerThreadExecutor.LabOutput2NextPartExecute))))
.Advance(-1)
.RemoveInstructions(2);
return matcher.InstructionEnumeration();
}
// Set rootLabId for LabComponent on lab stacking
[HarmonyTranspiler]
[HarmonyPatch(typeof(FactorySystem), nameof(FactorySystem.SetLabNextTarget))]
private static IEnumerable<CodeInstruction> FactorySystem_SetLabNextTarget_Transpier(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(EntityData), nameof(EntityData.labId))),
new CodeMatch(OpCodes.Stfld, AccessTools.Field(typeof(LabComponent), nameof(LabComponent.nextLabId))))
.Advance(2)
.Insert(new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldarg_1),
new CodeInstruction(OpCodes.Ldarg_2),
new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(LabOptPatchFunctions), nameof(LabOptPatchFunctions.SetRootLabIdForStacking))));
return matcher.InstructionEnumeration();
}
// Set rootLabId for LabComponent after loading game-save
[HarmonyPostfix]
[HarmonyPatch(typeof(FactorySystem), nameof(FactorySystem.Import))]
private static void FactorySystem_Import_Postfix(FactorySystem __instance)
{
LabOptPatchFunctions.SetRootLabIdOnLoading(__instance);
}
// Insert to root lab
[HarmonyTranspiler]
[HarmonyPatch(typeof(PlanetFactory), nameof(PlanetFactory.InsertInto))]
private static IEnumerable<CodeInstruction> PlanetFactory_InsertInto_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
var label1 = generator.DefineLabel();
matcher.MatchForward(false, new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(EntityData), nameof(EntityData.labId))))
.MatchForward(false, new CodeMatch(OpCodes.Ret))
.Advance(1)
.Insert(
// rootLabId = this.factorySystem.labPool[labId].rootLabId;
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(PlanetFactory), nameof(PlanetFactory.factorySystem))),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(FactorySystem), nameof(FactorySystem.labPool))),
new CodeInstruction(OpCodes.Ldloc_S, 5),
new CodeInstruction(OpCodes.Ldelema, typeof(LabComponent)),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(LabComponent), "rootLabId")),
new CodeInstruction(OpCodes.Stloc_S, 6),
// if (rootLabId <= 0) goto label1;
new CodeInstruction(OpCodes.Ldloc_S, 6),
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Ble, label1),
// labId = rootLabId;
new CodeInstruction(OpCodes.Ldloc_S, 6),
new CodeInstruction(OpCodes.Stloc_S, 5),
// entityId = this.factorySystem.labPool[labId].entityId;
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(PlanetFactory), nameof(PlanetFactory.factorySystem))),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(FactorySystem), nameof(FactorySystem.labPool))),
new CodeInstruction(OpCodes.Ldloc_S, 5),
new CodeInstruction(OpCodes.Ldelema, typeof(LabComponent)),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(LabComponent), nameof(LabComponent.entityId))),
new CodeInstruction(OpCodes.Starg_S, 1)
// lable1:
);
matcher.Instruction.labels.Add(label1);
return matcher.InstructionEnumeration();
}
// Fill into root lab
[HarmonyTranspiler]
[HarmonyPatch(typeof(PlanetFactory), nameof(PlanetFactory.EntityFastFillIn))]
private static IEnumerable<CodeInstruction> PlanetFactory_EntityFastFillIn_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
var label1 = generator.DefineLabel();
matcher.MatchForward(false, new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(FactorySystem), nameof(FactorySystem.labPool))))
.Advance(2)
.InsertAndAdvance(
// rootLabId = labPool[labId].rootLabId;
new CodeInstruction(OpCodes.Ldloc_S, 69),
new CodeInstruction(OpCodes.Ldloc_S, 68),
new CodeInstruction(OpCodes.Ldelema, typeof(LabComponent)),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(LabComponent), "rootLabId")),
new CodeInstruction(OpCodes.Stloc_S, 73),
// if (rootLabId <= 0) goto label1;
new CodeInstruction(OpCodes.Ldloc_S, 73),
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Ble, label1),
// labId = rootLabId;
new CodeInstruction(OpCodes.Ldloc_S, 73),
new CodeInstruction(OpCodes.Stloc_S, 68)
// lable1:
);
matcher.Instruction.labels.Add(label1);
return matcher.InstructionEnumeration();
}
// Fill into root lab
[HarmonyTranspiler]
[HarmonyPatch(typeof(UILabWindow), nameof(UILabWindow.OnItemButtonClick))]
private static IEnumerable<CodeInstruction> UILabWindow_OnItemButtonClick_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var matcher = new CodeMatcher(instructions, generator);
var local1 = generator.DeclareLocal(typeof(int));
var label1 = generator.DefineLabel();
matcher.MatchForward(false, new CodeMatch(OpCodes.Ret))
.Advance(1);
var labels = matcher.Instruction.labels;
matcher.InsertAndAdvance(
// labId = this.labId;
new CodeInstruction(OpCodes.Ldarg_0).WithLabels(labels),
new CodeInstruction(OpCodes.Call, AccessTools.PropertyGetter(typeof(UILabWindow), nameof(UILabWindow.labId))),
new CodeInstruction(OpCodes.Stloc_S, local1),
// rootLabId = this.factorySystem.labPool[labId].rootLabId;
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(UILabWindow), nameof(UILabWindow.factorySystem))),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(FactorySystem), nameof(FactorySystem.labPool))),
new CodeInstruction(OpCodes.Ldloc_S, local1),
new CodeInstruction(OpCodes.Ldelema, typeof(LabComponent)),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(LabComponent), "rootLabId")),
new CodeInstruction(OpCodes.Stloc_S, 27),
// if (rootLabId <= 0) goto label1;
new CodeInstruction(OpCodes.Ldloc_S, 27),
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Ble, label1),
// labId = rootLabId;
new CodeInstruction(OpCodes.Ldloc_S, 27),
new CodeInstruction(OpCodes.Stloc_S, local1)
// lable1:
);
matcher.Instruction.labels = new List<Label> { label1 };
for (;;)
{
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldarg_0),
new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(UILabWindow), nameof(UILabWindow.labId))));
if (matcher.IsInvalid) break;
var instr = new CodeInstruction(OpCodes.Ldloc_S, local1).WithLabels(matcher.Instruction.labels);
matcher.RemoveInstructions(2).InsertAndAdvance(instr);
}
return matcher.InstructionEnumeration();
}
// Display UI: use root lab's count
[HarmonyTranspiler]
[HarmonyPatch(typeof(UILabWindow), nameof(UILabWindow._OnUpdate))]
private static IEnumerable<CodeInstruction> UILabWindow__OnUpdate_Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
var local1 = generator.DeclareLocal(typeof(LabComponent).MakeByRefType());
var matcher = new CodeMatcher(instructions, generator);
matcher.End().MatchBack(false,
new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(LabComponent), nameof(LabComponent.matrixMode)))
).Advance(-1);
var labels = matcher.Instruction.labels;
var label1 = generator.DefineLabel();
var label2 = generator.DefineLabel();
matcher.InsertAndAdvance(
// rootLabId = labComponent.rootLabId;
new CodeInstruction(OpCodes.Ldloca_S, 0).WithLabels(labels),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(LabComponent), "rootLabId")),
new CodeInstruction(OpCodes.Stloc_S, 46),
// if (rootLabId <= 0) goto label1;
new CodeInstruction(OpCodes.Ldloc_S, 46),
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Ble, label1),
// labComponent2 = ref this.factorySystem.labPool[rootLabId];
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(UILabWindow), nameof(UILabWindow.factorySystem))),
new CodeInstruction(OpCodes.Ldfld, AccessTools.Field(typeof(FactorySystem), nameof(FactorySystem.labPool))),
new CodeInstruction(OpCodes.Ldloc_S, 46),
new CodeInstruction(OpCodes.Ldelema, typeof(LabComponent)),
new CodeInstruction(OpCodes.Stloc_S, local1),
new CodeInstruction(OpCodes.Br, label2),
// lable1:
// labComponent2 = ref labComponent;
new CodeInstruction(OpCodes.Ldloca_S, 0).WithLabels(label1),
new CodeInstruction(OpCodes.Stloc_S, local1)
);
matcher.Instruction.labels = new List<Label>{label2};
matcher.Advance(2);
var startPos = matcher.Pos;
for (;;)
{
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldloc_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(LabComponent), nameof(LabComponent.served)))
);
if (matcher.IsInvalid) break;
matcher.Set(OpCodes.Ldloc_S, local1).Advance(2);
}
matcher.Start().Advance(startPos);
for (;;)
{
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldloc_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(LabComponent), nameof(LabComponent.incServed)))
);
if (matcher.IsInvalid) break;
matcher.Set(OpCodes.Ldloc_S, local1).Advance(2);
}
matcher.Start().Advance(startPos);
for (;;)
{
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldloc_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(LabComponent), nameof(LabComponent.matrixServed)))
);
if (matcher.IsInvalid) break;
matcher.Set(OpCodes.Ldloc_S, local1).Advance(2);
}
matcher.Start().Advance(startPos);
for (;;)
{
matcher.MatchForward(false,
new CodeMatch(OpCodes.Ldloc_0),
new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(LabComponent), nameof(LabComponent.matrixIncServed)))
);
if (matcher.IsInvalid) break;
matcher.Set(OpCodes.Ldloc_S, local1).Advance(2);
}
return matcher.InstructionEnumeration();
}
}
public class LabOptPatchFunctions
{
private static readonly FieldInfo RootLabIdField = AccessTools.Field(typeof(LabComponent), "rootLabId");
public static void SetRootLabIdForStacking(FactorySystem factorySystem, int labId, int nextEntityId)
{
var rootId = (int)RootLabIdField.GetValue(factorySystem.labPool[labId]);
var targetLabId = factorySystem.factory.entityPool[nextEntityId].labId;
if (rootId <= 0) rootId = labId;
RootLabIdField.SetValueDirect(__makeref(factorySystem.labPool[targetLabId]), rootId);
LabOptPatch.Logger.LogDebug($"Set rootLabId of lab {targetLabId} to {rootId}");
}
public static void SetRootLabIdOnLoading(FactorySystem factorySystem)
{
var labCursor = factorySystem.labCursor;
var labPool = factorySystem.labPool;
var parentDict = new Dictionary<int, int>();
for (var id = 1; id < labCursor; id++)
{
if (labPool[id].id != id) continue;
ref var lab = ref labPool[id];
if (lab.nextLabId != 0) parentDict[lab.nextLabId] = id;
}
foreach (var pair in parentDict)
{
var rootId = pair.Value;
while (parentDict.TryGetValue(rootId, out var parentId)) rootId = parentId;
RootLabIdField.SetValueDirect(__makeref(labPool[pair.Key]), rootId);
LabOptPatch.Logger.LogDebug($"Set rootLabId of lab {pair.Key} to {rootId}");
}
}
}

27
LabOpt/LabOpt.csproj Normal file
View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AssemblyName>LabOpt</AssemblyName>
<BepInExPluginGuid>org.soardev.labopt</BepInExPluginGuid>
<Description>DSP MOD - LabOpt</Description>
<Version>0.1.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BepInEx.Core" Version="5.*" />
<PackageReference Include="BepInEx.PluginInfoProps" Version="1.*" />
<PackageReference Include="DysonSphereProgram.GameLibs" Version="*-r.*" />
<PackageReference Include="UnityEngine.Modules" Version="2018.4.12" IncludeAssets="compile" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'">
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="del /F /Q package\$(ProjectName)-$(Version).zip&#xA;IF NOT EXIST package\plugins (mkdir package\plugins)&#xA;copy /y $(TargetPath) package\plugins\&#xA;zip -9 -j package/$(ProjectName)-$(Version).zip package/icon.png package/manifest.json README.md&#xA;cd package&#xA;zip -9 -r $(ProjectName)-$(Version).zip patchers plugins" />
</Target>
</Project>

4
LabOpt/README.md Normal file
View File

@@ -0,0 +1,4 @@
# HideTips
#### Optimize Lab performance
#### 优化研究站性能

BIN
LabOpt/package/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -0,0 +1,9 @@
{
"name": "LabOpt",
"version_number": "0.1.0",
"website_url": "https://github.com/soarqin/DSP_Mods/tree/master/LabOpt",
"description": "Optimize Lab performance / 优化研究站性能",
"dependencies": [
"xiaoye97-BepInEx-5.4.17"
]
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BepInEx.Logging;
using HarmonyLib;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using MonoMod.Utils;
namespace LabOptPreloader;
public static class Preloader
{
private static readonly ManualLogSource Logger = BepInEx.Logging.Logger.CreateLogSource("ModFixerOne Preloader");
public static IEnumerable<string> TargetDLLs { get; } = new[] { "Assembly-CSharp.dll" };
public static void Patch(AssemblyDefinition assembly)
{
AddMemebers(assembly);
}
private static void AddMemebers(AssemblyDefinition assembly)
{
try
{
var gameModule = assembly.MainModule;
// Add field: int LabComponent.rootLabId;
gameModule.GetType("LabComponent").AddFied("rootLabId", gameModule.TypeSystem.Int32);
}
catch (Exception e)
{
Logger.LogError("Failed to add `int LabComponent.rootLabId`!");
Logger.LogError(e);
}
}
private static void AddFied(this TypeDefinition typeDefinition, string fieldName, TypeReference fieldType)
{
var newField = new FieldDefinition(fieldName, FieldAttributes.Public, fieldType);
typeDefinition.Fields.Add(newField);
Logger.LogDebug("Add " + newField);
}
}

View File

@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AssemblyName>LabOptPreloader</AssemblyName>
<Description>DSP MOD - Prealoder for LabOpt</Description>
<Version>0.1.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BepInEx.Core" Version="5.*" />
<PackageReference Include="BepInEx.PluginInfoProps" Version="1.*" />
<PackageReference Include="DysonSphereProgram.GameLibs" Version="*-r.*" />
<PackageReference Include="UnityEngine.Modules" Version="2018.4.12" IncludeAssets="compile" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`0123456789`))' == 'net'">
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="IF NOT EXIST ..\LabOpt\package\patchers (mkdir ..\LabOpt\package\patchers)&#xA;copy /y $(TargetPath) ..\LabOpt\package\patchers\" />
</Target>
</Project>

View File

@@ -0,0 +1,4 @@
# HideTips
#### Optimize Lab performance
#### 优化研究站性能