Revert "OBT1.1.0 Merge branch 'dev_pte' into dev"
This reverts commit177cf89756, reversing changes made to42ba733cd4.
This commit is contained in:
13
.github/workflows/publish-release.yml
vendored
13
.github/workflows/publish-release.yml
vendored
@@ -23,16 +23,13 @@ on:
|
|||||||
env:
|
env:
|
||||||
CI_DIR: 2049ef39-42a2-46d2-b513-ee6d2e3a7b15
|
CI_DIR: 2049ef39-42a2-46d2-b513-ee6d2e3a7b15
|
||||||
RELEASES: |
|
RELEASES: |
|
||||||
|
windows:client:Windows/Client
|
||||||
windows:server:Windows/Server
|
windows:server:Windows/Server
|
||||||
|
linux:client:Linux/Client
|
||||||
linux:server:Linux/Server
|
linux:server:Linux/Server
|
||||||
|
mac:client:Mac/Client/Barotrauma.app/Contents/MacOS
|
||||||
mac:server:Mac/Server
|
mac:server:Mac/Server
|
||||||
ARCHIVE_BASE_NAME: luacsforbarotraumaEP
|
ARCHIVE_BASE_NAME: luacsforbarotrauma
|
||||||
|
|
||||||
# windows:client:Windows/Client
|
|
||||||
# linux:client:Linux/Client
|
|
||||||
# mac:client:Mac/Client/Barotrauma.app/Contents/MacOS
|
|
||||||
# we do not currently provide a CL
|
|
||||||
|
|
||||||
# XXX: these file names are subject to shell expansion.
|
# XXX: these file names are subject to shell expansion.
|
||||||
# Be careful when using special characters.
|
# Be careful when using special characters.
|
||||||
ARCHIVE_FILES_SERVER: |
|
ARCHIVE_FILES_SERVER: |
|
||||||
@@ -214,4 +211,4 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
${{ env.CI_DIR }}/archives/${{ env.ARCHIVE_BASE_NAME }}_{build,patch}_{windows,linux,mac}_{client,server}.zip
|
${{ env.CI_DIR }}/archives/${{ env.ARCHIVE_BASE_NAME }}_{build,patch}_{windows,linux,mac}_{client,server}.zip
|
||||||
${{ env.CI_DIR }}/archives/${{ env.ARCHIVE_BASE_NAME }}_{build,patch}_linux_{client,server}.tar.gz
|
${{ env.CI_DIR }}/archives/${{ env.ARCHIVE_BASE_NAME }}_{build,patch}_linux_{client,server}.tar.gz
|
||||||
${{ env.CI_DIR }}/archives/${{ env.ARCHIVE_BASE_NAME }}_refs.zip
|
${{ env.CI_DIR }}/archives/${{ env.ARCHIVE_BASE_NAME }}_refs.zip
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -60,5 +60,3 @@ Deploy/DeployAll/PrivateKey.*
|
|||||||
#Rider
|
#Rider
|
||||||
*.DotSettings.user
|
*.DotSettings.user
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
.vscode/launch.json
|
|
||||||
.vscode/tasks.json
|
|
||||||
|
|||||||
@@ -2143,7 +2143,7 @@ namespace Barotrauma
|
|||||||
if (existingAffliction == null)
|
if (existingAffliction == null)
|
||||||
{
|
{
|
||||||
existingAffliction = afflictionPrefab.Instantiate(strength);
|
existingAffliction = afflictionPrefab.Instantiate(strength);
|
||||||
afflictions.TryAdd(existingAffliction, limb);
|
afflictions.Add(existingAffliction, limb);
|
||||||
newAdded = true;
|
newAdded = true;
|
||||||
}
|
}
|
||||||
existingAffliction.SetStrength(strength);
|
existingAffliction.SetStrength(strength);
|
||||||
|
|||||||
@@ -867,7 +867,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (var stackedItem in item.GetStackedItems())
|
foreach (var stackedItem in item.GetStackedItems())
|
||||||
{
|
{
|
||||||
Item.MarkForDeconstruction(stackedItem);
|
Item.DeconstructItems.Add(stackedItem);
|
||||||
}
|
}
|
||||||
HintManager.OnItemMarkedForDeconstruction(order.OrderGiver);
|
HintManager.OnItemMarkedForDeconstruction(order.OrderGiver);
|
||||||
}
|
}
|
||||||
@@ -875,7 +875,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (var stackedItem in item.GetStackedItems())
|
foreach (var stackedItem in item.GetStackedItems())
|
||||||
{
|
{
|
||||||
Item.UnmarkForDeconstruction(stackedItem);
|
Item.DeconstructItems.Remove(stackedItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1891,7 +1891,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Item.IsMarkedForDeconstruction(item) &&
|
else if (Item.DeconstructItems.Contains(item) &&
|
||||||
OrderPrefab.Prefabs.TryGet(Tags.DeconstructThis, out OrderPrefab deconstructOrder))
|
OrderPrefab.Prefabs.TryGet(Tags.DeconstructThis, out OrderPrefab deconstructOrder))
|
||||||
{
|
{
|
||||||
DrawSideIcon(deconstructOrder.SymbolSprite, Direction.Right, TextManager.Get("tooltip.markedfordeconstruction"), GUIStyle.Red, out bool mouseOn);
|
DrawSideIcon(deconstructOrder.SymbolSprite, Direction.Right, TextManager.Get("tooltip.markedfordeconstruction"), GUIStyle.Red, out bool mouseOn);
|
||||||
|
|||||||
@@ -471,11 +471,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (item0 == null && item1 != null)
|
if (item0 == null && item1 != null)
|
||||||
{
|
{
|
||||||
item0 = Item.ItemList.FirstOrDefault(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
|
item0 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
|
||||||
}
|
}
|
||||||
else if (item0 != null && item1 == null)
|
else if (item0 != null && item1 == null)
|
||||||
{
|
{
|
||||||
item1 = Item.ItemList.FirstOrDefault(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
|
item1 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
|
||||||
}
|
}
|
||||||
if (item0 != null && item1 != null && SelectedList.Contains(item0) && SelectedList.Contains(item1))
|
if (item0 != null && item1 != null && SelectedList.Contains(item0) && SelectedList.Contains(item1))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -503,102 +503,86 @@ namespace Barotrauma.Sounds
|
|||||||
mutex = new object();
|
mutex = new object();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the playingChannels lock to protect both channel assignment AND OpenAL operations.
|
|
||||||
// This prevents race conditions when multiple threads try to play sounds simultaneously
|
|
||||||
// (e.g., during Parallel.ForEach in MapEntity.UpdateAll).
|
|
||||||
int poolIndex = (int)sound.SourcePoolIndex;
|
|
||||||
object channelsLock = sound.Owner.GetPlayingChannelsLock(sound.SourcePoolIndex);
|
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
lock (channelsLock)
|
if (mutex != null) { Monitor.Enter(mutex); }
|
||||||
|
if (sound.Owner.CountPlayingInstances(sound) < sound.MaxSimultaneousInstances)
|
||||||
{
|
{
|
||||||
if (mutex != null) { Monitor.Enter(mutex); }
|
ALSourceIndex = sound.Owner.AssignFreeSourceToChannel(this);
|
||||||
try
|
}
|
||||||
|
|
||||||
|
if (ALSourceIndex >= 0)
|
||||||
|
{
|
||||||
|
if (!IsStream)
|
||||||
{
|
{
|
||||||
if (sound.Owner.CountPlayingInstancesUnsafe(sound, poolIndex) < sound.MaxSimultaneousInstances)
|
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, 0);
|
||||||
|
int alError = Al.GetError();
|
||||||
|
if (alError != Al.NoError)
|
||||||
{
|
{
|
||||||
ALSourceIndex = sound.Owner.AssignFreeSourceToChannelUnsafe(this, poolIndex);
|
throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ALSourceIndex >= 0)
|
Sound.FillAlBuffers();
|
||||||
|
if (Sound.Buffers is not { AlBuffer: not 0, AlMuffledBuffer: not 0 }) { return; }
|
||||||
|
|
||||||
|
uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffled ? Sound.Buffers.AlMuffledBuffer : Sound.Buffers.AlBuffer;
|
||||||
|
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
|
||||||
|
alError = Al.GetError();
|
||||||
|
if (alError != Al.NoError)
|
||||||
{
|
{
|
||||||
if (!IsStream)
|
throw new Exception("Failed to bind buffer to source (" + ALSourceIndex.ToString() + ":" + sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex) + "," + alBuffer.ToString() + "): " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
{
|
}
|
||||||
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, 0);
|
|
||||||
int alError = Al.GetError();
|
|
||||||
if (alError != Al.NoError)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError));
|
|
||||||
}
|
|
||||||
|
|
||||||
Sound.FillAlBuffers();
|
SetProperties();
|
||||||
if (Sound.Buffers is not { AlBuffer: not 0, AlMuffledBuffer: not 0 }) { return; }
|
|
||||||
|
|
||||||
uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffled ? Sound.Buffers.AlMuffledBuffer : Sound.Buffers.AlBuffer;
|
Al.SourcePlay(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex));
|
||||||
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
|
alError = Al.GetError();
|
||||||
alError = Al.GetError();
|
if (alError != Al.NoError)
|
||||||
if (alError != Al.NoError)
|
{
|
||||||
{
|
throw new Exception("Failed to play source: " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
throw new Exception("Failed to bind buffer to source (" + ALSourceIndex.ToString() + ":" + sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex) + "," + alBuffer.ToString() + "): " + debugName + ", " + Al.GetErrorString(alError));
|
|
||||||
}
|
|
||||||
|
|
||||||
SetProperties();
|
|
||||||
|
|
||||||
Al.SourcePlay(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex));
|
|
||||||
alError = Al.GetError();
|
|
||||||
if (alError != Al.NoError)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to play source: " + debugName + ", " + Al.GetErrorString(alError));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uint alBuffer = 0;
|
|
||||||
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
|
|
||||||
int alError = Al.GetError();
|
|
||||||
if (alError != Al.NoError)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError));
|
|
||||||
}
|
|
||||||
|
|
||||||
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Looping, Al.False);
|
|
||||||
alError = Al.GetError();
|
|
||||||
if (alError != Al.NoError)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to set stream looping state: " + debugName + ", " + Al.GetErrorString(alError));
|
|
||||||
}
|
|
||||||
|
|
||||||
streamShortBuffer = new short[STREAM_BUFFER_SIZE];
|
|
||||||
|
|
||||||
streamBuffers = new uint[4];
|
|
||||||
unqueuedBuffers = new uint[4];
|
|
||||||
streamBufferAmplitudes = new float[4];
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
Al.GenBuffer(out streamBuffers[i]);
|
|
||||||
|
|
||||||
alError = Al.GetError();
|
|
||||||
if (alError != Al.NoError)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to generate stream buffers: " + debugName + ", " + Al.GetErrorString(alError));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Al.IsBuffer(streamBuffers[i]))
|
|
||||||
{
|
|
||||||
throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid! " + debugName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Sound.Owner.InitUpdateChannelThread();
|
|
||||||
SetProperties();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
else
|
||||||
{
|
{
|
||||||
if (mutex != null) { Monitor.Exit(mutex); }
|
uint alBuffer = 0;
|
||||||
|
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
|
||||||
|
int alError = Al.GetError();
|
||||||
|
if (alError != Al.NoError)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
|
}
|
||||||
|
|
||||||
|
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Looping, Al.False);
|
||||||
|
alError = Al.GetError();
|
||||||
|
if (alError != Al.NoError)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to set stream looping state: " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
|
}
|
||||||
|
|
||||||
|
streamShortBuffer = new short[STREAM_BUFFER_SIZE];
|
||||||
|
|
||||||
|
streamBuffers = new uint[4];
|
||||||
|
unqueuedBuffers = new uint[4];
|
||||||
|
streamBufferAmplitudes = new float[4];
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
Al.GenBuffer(out streamBuffers[i]);
|
||||||
|
|
||||||
|
alError = Al.GetError();
|
||||||
|
if (alError != Al.NoError)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to generate stream buffers: " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Al.IsBuffer(streamBuffers[i]))
|
||||||
|
{
|
||||||
|
throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid! " + debugName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Sound.Owner.InitUpdateChannelThread();
|
||||||
|
SetProperties();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
@@ -607,6 +591,12 @@ namespace Barotrauma.Sounds
|
|||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
if (mutex != null) { Monitor.Exit(mutex); }
|
||||||
|
#if !DEBUG
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void SetProperties()
|
void SetProperties()
|
||||||
|
|||||||
@@ -417,15 +417,6 @@ namespace Barotrauma.Sounds
|
|||||||
return sourcePools[(int)poolIndex].ALSources[srcInd];
|
return sourcePools[(int)poolIndex].ALSources[srcInd];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the lock object for the playing channels array for a specific pool.
|
|
||||||
/// Used to protect OpenAL operations that need to be atomic with channel assignment.
|
|
||||||
/// </summary>
|
|
||||||
public object GetPlayingChannelsLock(SourcePoolIndex poolIndex)
|
|
||||||
{
|
|
||||||
return playingChannels[(int)poolIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int AssignFreeSourceToChannel(SoundChannel newChannel)
|
public int AssignFreeSourceToChannel(SoundChannel newChannel)
|
||||||
{
|
{
|
||||||
if (Disabled) { return -1; }
|
if (Disabled) { return -1; }
|
||||||
@@ -436,25 +427,14 @@ namespace Barotrauma.Sounds
|
|||||||
|
|
||||||
lock (playingChannels[poolIndex])
|
lock (playingChannels[poolIndex])
|
||||||
{
|
{
|
||||||
return AssignFreeSourceToChannelUnsafe(newChannel, poolIndex);
|
for (int i = 0; i < playingChannels[poolIndex].Length; i++)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Assigns a free source to a channel without locking.
|
|
||||||
/// Caller MUST hold the playingChannels[poolIndex] lock before calling this method.
|
|
||||||
/// </summary>
|
|
||||||
public int AssignFreeSourceToChannelUnsafe(SoundChannel newChannel, int poolIndex)
|
|
||||||
{
|
|
||||||
if (Disabled) { return -1; }
|
|
||||||
|
|
||||||
for (int i = 0; i < playingChannels[poolIndex].Length; i++)
|
|
||||||
{
|
|
||||||
if (playingChannels[poolIndex][i] == null || !playingChannels[poolIndex][i].IsPlaying)
|
|
||||||
{
|
{
|
||||||
if (playingChannels[poolIndex][i] != null) { playingChannels[poolIndex][i].Dispose(); }
|
if (playingChannels[poolIndex][i] == null || !playingChannels[poolIndex][i].IsPlaying)
|
||||||
playingChannels[poolIndex][i] = newChannel;
|
{
|
||||||
return i;
|
if (playingChannels[poolIndex][i] != null) { playingChannels[poolIndex][i].Dispose(); }
|
||||||
|
playingChannels[poolIndex][i] = newChannel;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,25 +476,13 @@ namespace Barotrauma.Sounds
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
lock (playingChannels[(int)sound.SourcePoolIndex])
|
lock (playingChannels[(int)sound.SourcePoolIndex])
|
||||||
{
|
{
|
||||||
count = CountPlayingInstancesUnsafe(sound, (int)sound.SourcePoolIndex);
|
for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Counts playing instances without locking.
|
|
||||||
/// Caller MUST hold the playingChannels[poolIndex] lock before calling this method.
|
|
||||||
/// </summary>
|
|
||||||
public int CountPlayingInstancesUnsafe(Sound sound, int poolIndex)
|
|
||||||
{
|
|
||||||
if (Disabled) { return 0; }
|
|
||||||
int count = 0;
|
|
||||||
for (int i = 0; i < playingChannels[poolIndex].Length; i++)
|
|
||||||
{
|
|
||||||
if (playingChannels[poolIndex][i] != null &&
|
|
||||||
playingChannels[poolIndex][i].Sound.Filename == sound.Filename)
|
|
||||||
{
|
{
|
||||||
if (playingChannels[poolIndex][i].IsPlaying) { count++; };
|
if (playingChannels[(int)sound.SourcePoolIndex][i] != null &&
|
||||||
|
playingChannels[(int)sound.SourcePoolIndex][i].Sound.Filename == sound.Filename)
|
||||||
|
{
|
||||||
|
if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) { count++; };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ namespace Barotrauma.Networking
|
|||||||
if (!MathUtils.NearlyEqual(karma, syncedKarma, 10.0f))
|
if (!MathUtils.NearlyEqual(karma, syncedKarma, 10.0f))
|
||||||
{
|
{
|
||||||
syncedKarma = karma;
|
syncedKarma = karma;
|
||||||
GameMain.NetworkMember.IncrementLastClientListUpdateID();
|
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ namespace Barotrauma.Networking
|
|||||||
StartTime = DateTime.Now;
|
StartTime = DateTime.Now;
|
||||||
|
|
||||||
OnStarted(transfer);
|
OnStarted(transfer);
|
||||||
GameMain.Server.IncrementLastClientListUpdateID();
|
GameMain.Server.LastClientListUpdateID++;
|
||||||
|
|
||||||
return transfer;
|
return transfer;
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
if (numRemoved > 0 || endedTransfers.Count > 0)
|
if (numRemoved > 0 || endedTransfers.Count > 0)
|
||||||
{
|
{
|
||||||
GameMain.Server.IncrementLastClientListUpdateID();
|
GameMain.Server.LastClientListUpdateID++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IncrementLastClientListUpdateID();
|
LastClientListUpdateID++;
|
||||||
|
|
||||||
if (newClient.Connection == OwnerConnection && OwnerConnection != null)
|
if (newClient.Connection == OwnerConnection && OwnerConnection != null)
|
||||||
{
|
{
|
||||||
@@ -3222,7 +3222,7 @@ namespace Barotrauma.Networking
|
|||||||
initiatedStartGame = false;
|
initiatedStartGame = false;
|
||||||
GameMain.ResetFrameTime();
|
GameMain.ResetFrameTime();
|
||||||
|
|
||||||
IncrementLastClientListUpdateID();
|
LastClientListUpdateID++;
|
||||||
|
|
||||||
roundStartTime = DateTime.Now;
|
roundStartTime = DateTime.Now;
|
||||||
|
|
||||||
@@ -3532,7 +3532,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
var coolDownRemaining = Client.NameChangeCoolDown - timeSinceNameChange;
|
var coolDownRemaining = Client.NameChangeCoolDown - timeSinceNameChange;
|
||||||
SendDirectChatMessage($"ServerMessage.NameChangeFailedCooldownActive~[seconds]={(int)coolDownRemaining.TotalSeconds}", c);
|
SendDirectChatMessage($"ServerMessage.NameChangeFailedCooldownActive~[seconds]={(int)coolDownRemaining.TotalSeconds}", c);
|
||||||
IncrementLastClientListUpdateID();
|
LastClientListUpdateID++;
|
||||||
//increment the ID to make sure the current server-side name is treated as the "latest",
|
//increment the ID to make sure the current server-side name is treated as the "latest",
|
||||||
//and the client correctly reverts back to the old name
|
//and the client correctly reverts back to the old name
|
||||||
c.NameId++;
|
c.NameId++;
|
||||||
@@ -3545,7 +3545,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
IncrementLastClientListUpdateID();
|
LastClientListUpdateID++;
|
||||||
return result.Value;
|
return result.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3562,14 +3562,14 @@ namespace Barotrauma.Networking
|
|||||||
c.Name = newName;
|
c.Name = newName;
|
||||||
c.RejectedName = string.Empty;
|
c.RejectedName = string.Empty;
|
||||||
SendChatMessage($"ServerMessage.NameChangeSuccessful~[oldname]={oldName}~[newname]={newName}", ChatMessageType.Server);
|
SendChatMessage($"ServerMessage.NameChangeSuccessful~[oldname]={oldName}~[newname]={newName}", ChatMessageType.Server);
|
||||||
IncrementLastClientListUpdateID();
|
LastClientListUpdateID++;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//update client list even if the name cannot be changed to the one sent by the client,
|
//update client list even if the name cannot be changed to the one sent by the client,
|
||||||
//so the client will be informed what their actual name is
|
//so the client will be informed what their actual name is
|
||||||
IncrementLastClientListUpdateID();
|
LastClientListUpdateID++;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4857,7 +4857,7 @@ namespace Barotrauma.Networking
|
|||||||
private void UpdateClientLobbies()
|
private void UpdateClientLobbies()
|
||||||
{
|
{
|
||||||
// Triggers a call to WriteClientList(), which causes clients to call GameClient.ReadClientList()
|
// Triggers a call to WriteClientList(), which causes clients to call GameClient.ReadClientList()
|
||||||
IncrementLastClientListUpdateID();
|
LastClientListUpdateID++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Client> GetPlayingClients()
|
private List<Client> GetPlayingClients()
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
client.Character.CharacterHealth.ApplyAffliction(null, new Affliction(herpesAffliction, herpesStrength));
|
client.Character.CharacterHealth.ApplyAffliction(null, new Affliction(herpesAffliction, herpesStrength));
|
||||||
GameServer.Log($"{GameServer.ClientLogName(client)} has contracted space herpes due to low karma.", ServerLog.MessageType.Karma);
|
GameServer.Log($"{GameServer.ClientLogName(client)} has contracted space herpes due to low karma.", ServerLog.MessageType.Karma);
|
||||||
GameMain.NetworkMember.IncrementLastClientListUpdateID();
|
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||||
}
|
}
|
||||||
else if (existingAffliction != null)
|
else if (existingAffliction != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ namespace Barotrauma.Networking
|
|||||||
var shuttleGaps = Gap.GapList.FindAll(g => RespawnShuttles.Contains(g.Submarine) && g.ConnectedWall != null);
|
var shuttleGaps = Gap.GapList.FindAll(g => RespawnShuttles.Contains(g.Submarine) && g.ConnectedWall != null);
|
||||||
shuttleGaps.ForEach(g => Spawner.AddEntityToRemoveQueue(g));
|
shuttleGaps.ForEach(g => Spawner.AddEntityToRemoveQueue(g));
|
||||||
|
|
||||||
var dockingPorts = Item.ItemList.Where(i => RespawnShuttles.Contains(i.Submarine) && i.GetComponent<DockingPort>() != null).ToList();
|
var dockingPorts = Item.ItemList.FindAll(i => RespawnShuttles.Contains(i.Submarine) && i.GetComponent<DockingPort>() != null);
|
||||||
dockingPorts.ForEach(d => d.GetComponent<DockingPort>().Undock());
|
dockingPorts.ForEach(d => d.GetComponent<DockingPort>().Undock());
|
||||||
|
|
||||||
if (!IsShuttleInsideLevel || DateTime.Now > teamSpecificState.DespawnTime)
|
if (!IsShuttleInsideLevel || DateTime.Now > teamSpecificState.DespawnTime)
|
||||||
|
|||||||
@@ -156,8 +156,7 @@ namespace Barotrauma
|
|||||||
Reactor reactor = item.GetComponent<Reactor>();
|
Reactor reactor = item.GetComponent<Reactor>();
|
||||||
if (reactor != null && reactor.Item.Condition > 0.0f) { roundData.Reactors.Add(reactor); }
|
if (reactor != null && reactor.Item.Condition > 0.0f) { roundData.Reactors.Add(reactor); }
|
||||||
}
|
}
|
||||||
|
pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||||
pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false);
|
|
||||||
cachedDistances.Clear();
|
cachedDistances.Clear();
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
@@ -324,7 +323,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
static CachedDistance CalculateNewCachedDistance(Character c)
|
static CachedDistance CalculateNewCachedDistance(Character c)
|
||||||
{
|
{
|
||||||
pathFinder ??= new PathFinder(WayPoint.WayPointList.ToList(), false);
|
pathFinder ??= new PathFinder(WayPoint.WayPointList, false);
|
||||||
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(c.WorldPosition), ConvertUnits.ToSimUnits(Submarine.MainSub.WorldPosition));
|
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(c.WorldPosition), ConvertUnits.ToSimUnits(Submarine.MainSub.WorldPosition));
|
||||||
if (path.Unreachable) { return null; }
|
if (path.Unreachable) { return null; }
|
||||||
return new CachedDistance(c.WorldPosition, Submarine.MainSub.WorldPosition, path.TotalLength, Timing.TotalTime + Rand.Range(1.0f, 5.0f));
|
return new CachedDistance(c.WorldPosition, Submarine.MainSub.WorldPosition, path.TotalLength, Timing.TotalTime + Rand.Range(1.0f, 5.0f));
|
||||||
|
|||||||
@@ -1,67 +1,13 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for AITarget list operations.
|
|
||||||
/// Uses copy-on-write pattern for lock-free reads.
|
|
||||||
/// </summary>
|
|
||||||
class ThreadSafeAITargetList : IEnumerable<AITarget>
|
|
||||||
{
|
|
||||||
private volatile List<AITarget> _list = new List<AITarget>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(AITarget target)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<AITarget>(_list) { target };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(AITarget target)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<AITarget>(_list);
|
|
||||||
bool removed = newList.Remove(target);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<AITarget>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(AITarget target) => _list.Contains(target);
|
|
||||||
|
|
||||||
public AITarget this[int index] => _list[index];
|
|
||||||
|
|
||||||
public IEnumerator<AITarget> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
public List<AITarget> ToList() => new List<AITarget>(_list);
|
|
||||||
public AITarget FirstOrDefault(Func<AITarget, bool> predicate) => _list.FirstOrDefault(predicate);
|
|
||||||
public IEnumerable<AITarget> Where(Func<AITarget, bool> predicate) => _list.Where(predicate);
|
|
||||||
public bool Any(Func<AITarget, bool> predicate) => _list.Any(predicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
partial class AITarget
|
partial class AITarget
|
||||||
{
|
{
|
||||||
public static ThreadSafeAITargetList List = new ThreadSafeAITargetList();
|
public static List<AITarget> List = new List<AITarget>();
|
||||||
|
|
||||||
private Entity entity;
|
private Entity entity;
|
||||||
public Entity Entity
|
public Entity Entity
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Microsoft.Xna.Framework;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -1818,9 +1817,7 @@ namespace Barotrauma
|
|||||||
public static bool HasDivingMask(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
|
public static bool HasDivingMask(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
|
||||||
=> HasItem(character, Tags.LightDivingGear, out _, requireOxygenTank ? Tags.OxygenSource : Identifier.Empty, conditionPercentage, requireEquipped: true);
|
=> HasItem(character, Tags.LightDivingGear, out _, requireOxygenTank ? Tags.OxygenSource : Identifier.Empty, conditionPercentage, requireEquipped: true);
|
||||||
|
|
||||||
// ThreadLocal to ensure thread safety - each thread gets its own list instance
|
private static List<Item> matchingItems = new List<Item>();
|
||||||
private static readonly ThreadLocal<List<Item>> matchingItemsLocal = new ThreadLocal<List<Item>>(() => new List<Item>());
|
|
||||||
private static List<Item> matchingItems => matchingItemsLocal.Value;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Note: uses a single list for matching items. The item is reused each time when the method is called. So if you use the method twice, and then refer to the first items, you'll actually get the second.
|
/// Note: uses a single list for matching items. The item is reused each time when the method is called. So if you use the method twice, and then refer to the first items, you'll actually get the second.
|
||||||
@@ -1828,16 +1825,15 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool HasItem(Character character, Identifier tagOrIdentifier, out IEnumerable<Item> items, Identifier containedTag = default, float conditionPercentage = 0, bool requireEquipped = false, bool recursive = true, Func<Item, bool> predicate = null)
|
public static bool HasItem(Character character, Identifier tagOrIdentifier, out IEnumerable<Item> items, Identifier containedTag = default, float conditionPercentage = 0, bool requireEquipped = false, bool recursive = true, Func<Item, bool> predicate = null)
|
||||||
{
|
{
|
||||||
var localMatchingItems = matchingItems;
|
matchingItems.Clear();
|
||||||
localMatchingItems.Clear();
|
items = matchingItems;
|
||||||
items = localMatchingItems;
|
|
||||||
if (character?.Inventory == null) { return false; }
|
if (character?.Inventory == null) { return false; }
|
||||||
character.Inventory.FindAllItems(i => (i.Prefab.Identifier == tagOrIdentifier || i.HasTag(tagOrIdentifier)) &&
|
matchingItems = character.Inventory.FindAllItems(i => (i.Prefab.Identifier == tagOrIdentifier || i.HasTag(tagOrIdentifier)) &&
|
||||||
i.ConditionPercentage >= conditionPercentage &&
|
i.ConditionPercentage >= conditionPercentage &&
|
||||||
(!requireEquipped || character.HasEquippedItem(i)) &&
|
(!requireEquipped || character.HasEquippedItem(i)) &&
|
||||||
(predicate == null || predicate(i)), recursive, localMatchingItems);
|
(predicate == null || predicate(i)), recursive, matchingItems);
|
||||||
items = localMatchingItems;
|
items = matchingItems;
|
||||||
foreach (var item in localMatchingItems)
|
foreach (var item in matchingItems)
|
||||||
{
|
{
|
||||||
if (item == null) { continue; }
|
if (item == null) { continue; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Barotrauma.IO;
|
using Barotrauma.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -10,8 +9,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
class NPCConversationCollection : Prefab
|
class NPCConversationCollection : Prefab
|
||||||
{
|
{
|
||||||
// Thread-safe dictionary for language-based collections
|
public static readonly Dictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>> Collections = new Dictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>>();
|
||||||
public static readonly ConcurrentDictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>> Collections = new ConcurrentDictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>>();
|
|
||||||
|
|
||||||
public readonly LanguageIdentifier Language;
|
public readonly LanguageIdentifier Language;
|
||||||
|
|
||||||
@@ -162,24 +160,7 @@ namespace Barotrauma
|
|||||||
return currentFlags;
|
return currentFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-safe previous conversations tracking using copy-on-write pattern
|
private static readonly List<NPCConversation> previousConversations = new List<NPCConversation>();
|
||||||
private static volatile List<NPCConversation> _previousConversations = new List<NPCConversation>();
|
|
||||||
private static readonly object _previousConversationsLock = new object();
|
|
||||||
private static List<NPCConversation> previousConversations => _previousConversations;
|
|
||||||
|
|
||||||
private static void AddToPreviousConversations(NPCConversation conversation)
|
|
||||||
{
|
|
||||||
lock (_previousConversationsLock)
|
|
||||||
{
|
|
||||||
var newList = new List<NPCConversation>(_previousConversations);
|
|
||||||
newList.Insert(0, conversation);
|
|
||||||
if (newList.Count > MaxPreviousConversations)
|
|
||||||
{
|
|
||||||
newList.RemoveAt(MaxPreviousConversations);
|
|
||||||
}
|
|
||||||
_previousConversations = newList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<(Character speaker, string line)> CreateRandom(List<Character> availableSpeakers)
|
public static List<(Character speaker, string line)> CreateRandom(List<Character> availableSpeakers)
|
||||||
{
|
{
|
||||||
@@ -300,7 +281,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (baseConversation == null)
|
if (baseConversation == null)
|
||||||
{
|
{
|
||||||
AddToPreviousConversations(selectedConversation);
|
previousConversations.Insert(0, selectedConversation);
|
||||||
|
if (previousConversations.Count > MaxPreviousConversations) previousConversations.RemoveAt(MaxPreviousConversations);
|
||||||
}
|
}
|
||||||
lineList.Add((speaker, selectedConversation.Line));
|
lineList.Add((speaker, selectedConversation.Line));
|
||||||
CreateConversation(availableSpeakers, assignedSpeakers, selectedConversation, lineList, availableConversations);
|
CreateConversation(availableSpeakers, assignedSpeakers, selectedConversation, lineList, availableConversations);
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override bool CheckObjectiveState()
|
protected override bool CheckObjectiveState()
|
||||||
{
|
{
|
||||||
if (item.IgnoreByAI(character) || Item.IsMarkedForDeconstruction(item))
|
if (item.IgnoreByAI(character) || Item.DeconstructItems.Contains(item))
|
||||||
{
|
{
|
||||||
Abandon = true;
|
Abandon = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ namespace Barotrauma
|
|||||||
if (!allowUnloading) { return false; }
|
if (!allowUnloading) { return false; }
|
||||||
if (requireValidContainer && !IsValidContainer(item.Container, character)) { return false; }
|
if (requireValidContainer && !IsValidContainer(item.Container, character)) { return false; }
|
||||||
}
|
}
|
||||||
if (ignoreItemsMarkedForDeconstruction && Item.IsMarkedForDeconstruction(item)) { return false; }
|
if (ignoreItemsMarkedForDeconstruction && Item.DeconstructItems.Contains(item)) { return false; }
|
||||||
if (!item.HasAccess(character)) { return false; }
|
if (!item.HasAccess(character)) { return false; }
|
||||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||||
if (item.HasBallastFloraInHull) { return false; }
|
if (item.HasBallastFloraInHull) { return false; }
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
@@ -69,9 +68,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When did the character last inspect whether some other character has stolen items on them?
|
/// When did the character last inspect whether some other character has stolen items on them?
|
||||||
/// Thread-safe dictionary for concurrent access.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly ConcurrentDictionary<Character, double> lastInspectionTimes = new ConcurrentDictionary<Character, double>();
|
private static readonly Dictionary<Character, double> lastInspectionTimes = new Dictionary<Character, double>();
|
||||||
|
|
||||||
private const float NormalInspectionInterval = 120.0f;
|
private const float NormalInspectionInterval = 120.0f;
|
||||||
private const float CriminalInspectionInterval = 30.0f;
|
private const float CriminalInspectionInterval = 30.0f;
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct)
|
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct)
|
||||||
{
|
{
|
||||||
if (item.AllowDeconstruct && !Item.IsMarkedForDeconstruction(item) &&
|
if (item.AllowDeconstruct && !Item.DeconstructItems.Contains(item) &&
|
||||||
//only allow deconstructing if there are no deconstruction recipes (= deconstructing yields nothing), or deconstruction recipes that
|
//only allow deconstructing if there are no deconstruction recipes (= deconstructing yields nothing), or deconstruction recipes that
|
||||||
(item.Prefab.DeconstructItems.None() ||
|
(item.Prefab.DeconstructItems.None() ||
|
||||||
item.Prefab.DeconstructItems.Any(deconstructItem =>
|
item.Prefab.DeconstructItems.Any(deconstructItem =>
|
||||||
@@ -454,7 +454,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else if (Identifier == Tags.DontDeconstructThis)
|
else if (Identifier == Tags.DontDeconstructThis)
|
||||||
{
|
{
|
||||||
if (Item.IsMarkedForDeconstruction(item)) { return true; }
|
if (Item.DeconstructItems.Contains(item)) { return true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
ImmutableArray<Identifier> targetItems = GetTargetItems(option);
|
ImmutableArray<Identifier> targetItems = GetTargetItems(option);
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ using FarseerPhysics.Dynamics.Contacts;
|
|||||||
using FarseerPhysics.Dynamics.Joints;
|
using FarseerPhysics.Dynamics.Joints;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using LimbParams = Barotrauma.RagdollParams.LimbParams;
|
using LimbParams = Barotrauma.RagdollParams.LimbParams;
|
||||||
@@ -27,33 +25,7 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
const float MaxImpactDamage = 0.1f;
|
const float MaxImpactDamage = 0.1f;
|
||||||
|
|
||||||
// Thread-safe list using copy-on-write pattern (ConcurrentBag doesn't support indexer/Remove)
|
private static readonly List<Ragdoll> list = new List<Ragdoll>();
|
||||||
private static volatile List<Ragdoll> _list = new List<Ragdoll>();
|
|
||||||
private static readonly object _listLock = new object();
|
|
||||||
private static List<Ragdoll> list => _list;
|
|
||||||
|
|
||||||
private static void ListAdd(Ragdoll ragdoll)
|
|
||||||
{
|
|
||||||
lock (_listLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Ragdoll>(_list) { ragdoll };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ListRemove(Ragdoll ragdoll)
|
|
||||||
{
|
|
||||||
lock (_listLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Ragdoll>(_list);
|
|
||||||
bool removed = newList.Remove(ragdoll);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Impact
|
struct Impact
|
||||||
{
|
{
|
||||||
@@ -73,8 +45,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-safe queue for physics collision callbacks
|
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
|
||||||
private readonly ConcurrentQueue<Impact> impactQueue = new ConcurrentQueue<Impact>();
|
|
||||||
|
|
||||||
protected Hull currentHull;
|
protected Hull currentHull;
|
||||||
|
|
||||||
@@ -496,7 +467,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public Ragdoll(Character character, string seed, RagdollParams ragdollParams = null)
|
public Ragdoll(Character character, string seed, RagdollParams ragdollParams = null)
|
||||||
{
|
{
|
||||||
ListAdd(this);
|
list.Add(this);
|
||||||
this.character = character;
|
this.character = character;
|
||||||
Recreate(ragdollParams ?? RagdollParams);
|
Recreate(ragdollParams ?? RagdollParams);
|
||||||
}
|
}
|
||||||
@@ -773,7 +744,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!f2.IsSensor)
|
if (!f2.IsSensor)
|
||||||
{
|
{
|
||||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
lock (impactQueue)
|
||||||
|
{
|
||||||
|
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -845,7 +819,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
lock (impactQueue)
|
||||||
|
{
|
||||||
|
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1297,8 +1274,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!character.Enabled || character.Removed || Frozen || Invalid || Collider == null || Collider.Removed) { return; }
|
if (!character.Enabled || character.Removed || Frozen || Invalid || Collider == null || Collider.Removed) { return; }
|
||||||
|
|
||||||
while (impactQueue.TryDequeue(out var impact))
|
while (impactQueue.Count > 0)
|
||||||
{
|
{
|
||||||
|
var impact = impactQueue.Dequeue();
|
||||||
ApplyImpact(impact.F1, impact.F2, impact.LocalNormal, impact.ImpactPos, impact.Velocity);
|
ApplyImpact(impact.F1, impact.F2, impact.LocalNormal, impact.ImpactPos, impact.Velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2347,7 +2325,7 @@ namespace Barotrauma
|
|||||||
LimbJoints = null;
|
LimbJoints = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ListRemove(this);
|
list.Remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RemoveAll()
|
public static void RemoveAll()
|
||||||
|
|||||||
@@ -7,12 +7,10 @@ using FarseerPhysics;
|
|||||||
using FarseerPhysics.Dynamics;
|
using FarseerPhysics.Dynamics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
#if SERVER
|
#if SERVER
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -30,70 +28,12 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public readonly record struct TalentResistanceIdentifier(Identifier ResistanceIdentifier, Identifier TalentIdentifier);
|
public readonly record struct TalentResistanceIdentifier(Identifier ResistanceIdentifier, Identifier TalentIdentifier);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for character list operations.
|
|
||||||
/// Provides lock-free read operations and synchronized write operations.
|
|
||||||
/// </summary>
|
|
||||||
class ThreadSafeCharacterList : IEnumerable<Character>
|
|
||||||
{
|
|
||||||
private volatile List<Character> _list = new List<Character>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(Character character)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Character>(_list) { character };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(Character character)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Character>(_list);
|
|
||||||
bool removed = newList.Remove(character);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<Character>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(Character character) => _list.Contains(character);
|
|
||||||
|
|
||||||
public Character this[int index] => _list[index];
|
|
||||||
|
|
||||||
public IEnumerator<Character> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// LINQ-friendly snapshot for complex queries
|
|
||||||
public List<Character> ToList() => new List<Character>(_list);
|
|
||||||
|
|
||||||
public Character FirstOrDefault(Func<Character, bool> predicate) => _list.FirstOrDefault(predicate);
|
|
||||||
public Character Find(Predicate<Character> predicate) => _list.Find(predicate);
|
|
||||||
public List<Character> FindAll(Predicate<Character> predicate) => _list.FindAll(predicate);
|
|
||||||
public IEnumerable<Character> Where(Func<Character, bool> predicate) => _list.Where(predicate);
|
|
||||||
public bool Any(Func<Character, bool> predicate) => _list.Any(predicate);
|
|
||||||
public bool None(Func<Character, bool> predicate) => !_list.Any(predicate);
|
|
||||||
public int CountWhere(Func<Character, bool> predicate) => _list.Count(predicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync
|
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync
|
||||||
{
|
{
|
||||||
public static readonly ThreadSafeCharacterList CharacterList = new ThreadSafeCharacterList();
|
public static readonly List<Character> CharacterList = new List<Character>();
|
||||||
|
|
||||||
public static int CharacterUpdateInterval = 1;
|
public static int CharacterUpdateInterval = 1;
|
||||||
private static volatile int characterUpdateTick = 1;
|
private static int characterUpdateTick = 1;
|
||||||
|
|
||||||
public const float MaxHighlightDistance = 150.0f;
|
public const float MaxHighlightDistance = 150.0f;
|
||||||
public const float MaxDragDistance = 200.0f;
|
public const float MaxDragDistance = 200.0f;
|
||||||
@@ -2821,11 +2761,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
int itemsPerFrame = IsOnPlayerTeam ? 100 : 10;
|
int itemsPerFrame = IsOnPlayerTeam ? 100 : 10;
|
||||||
int checkedItemCount = 0;
|
int checkedItemCount = 0;
|
||||||
var cachedItems = Item.GetCachedItemList();
|
for (int i = 0; i < itemsPerFrame && itemIndex < Item.ItemList.Count; i++, itemIndex++)
|
||||||
for (int i = 0; i < itemsPerFrame && itemIndex < cachedItems.Count; i++, itemIndex++)
|
|
||||||
{
|
{
|
||||||
checkedItemCount++;
|
checkedItemCount++;
|
||||||
var item = cachedItems[itemIndex];
|
var item = Item.ItemList[itemIndex];
|
||||||
if (!item.IsInteractable(this)) { continue; }
|
if (!item.IsInteractable(this)) { continue; }
|
||||||
if (ignoredItems != null && ignoredItems.Contains(item)) { continue; }
|
if (ignoredItems != null && ignoredItems.Contains(item)) { continue; }
|
||||||
if (item.Submarine == null) { continue; }
|
if (item.Submarine == null) { continue; }
|
||||||
@@ -2861,10 +2800,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetItem = _foundItem;
|
targetItem = _foundItem;
|
||||||
bool completed = itemIndex >= cachedItems.Count - 1;
|
bool completed = itemIndex >= Item.ItemList.Count - 1;
|
||||||
if (HumanAIController.DebugAI && checkedItemCount > 0 && targetItem != null && StopWatch.ElapsedMilliseconds > 1)
|
if (HumanAIController.DebugAI && checkedItemCount > 0 && targetItem != null && StopWatch.ElapsedMilliseconds > 1)
|
||||||
{
|
{
|
||||||
var msg = $"Went through {checkedItemCount} of total {cachedItems.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {completed}";
|
var msg = $"Went through {checkedItemCount} of total {Item.ItemList.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {completed}";
|
||||||
if (StopWatch.ElapsedMilliseconds > 5)
|
if (StopWatch.ElapsedMilliseconds > 5)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError(msg);
|
DebugConsole.ThrowError(msg);
|
||||||
@@ -4880,11 +4819,7 @@ namespace Barotrauma
|
|||||||
HealthUpdateInterval = 0.0f;
|
HealthUpdateInterval = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-static to avoid concurrent modification in parallel item updates
|
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||||
[ThreadStatic]
|
|
||||||
private static List<ISerializableEntity> t_statusEffectTargets;
|
|
||||||
private static List<ISerializableEntity> StatusEffectTargets => t_statusEffectTargets ??= new List<ISerializableEntity>();
|
|
||||||
|
|
||||||
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
||||||
{
|
{
|
||||||
if (actionType == ActionType.OnEating)
|
if (actionType == ActionType.OnEating)
|
||||||
@@ -4913,7 +4848,6 @@ namespace Barotrauma
|
|||||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||||
{
|
{
|
||||||
var targets = StatusEffectTargets;
|
|
||||||
targets.Clear();
|
targets.Clear();
|
||||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||||
statusEffect.Apply(actionType, deltaTime, this, targets);
|
statusEffect.Apply(actionType, deltaTime, this, targets);
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ using Barotrauma.Extensions;
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
using System.Globalization;
|
||||||
using MoonSharp.Interpreter;
|
using MoonSharp.Interpreter;
|
||||||
|
using Barotrauma.Abilities;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -130,9 +132,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private readonly List<LimbHealth> limbHealths = new List<LimbHealth>();
|
private readonly List<LimbHealth> limbHealths = new List<LimbHealth>();
|
||||||
|
|
||||||
// Thread-safe afflictions dictionary for concurrent access
|
private readonly Dictionary<Affliction, LimbHealth> afflictions = new Dictionary<Affliction, LimbHealth>();
|
||||||
private readonly ConcurrentDictionary<Affliction, LimbHealth> afflictions = new ConcurrentDictionary<Affliction, LimbHealth>();
|
private readonly HashSet<Affliction> irremovableAfflictions = new HashSet<Affliction>();
|
||||||
private readonly ConcurrentDictionary<Affliction, byte> irremovableAfflictions = new ConcurrentDictionary<Affliction, byte>();
|
|
||||||
private Affliction bloodlossAffliction;
|
private Affliction bloodlossAffliction;
|
||||||
private Affliction oxygenLowAffliction;
|
private Affliction oxygenLowAffliction;
|
||||||
private Affliction pressureAffliction;
|
private Affliction pressureAffliction;
|
||||||
@@ -323,13 +324,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void InitIrremovableAfflictions()
|
private void InitIrremovableAfflictions()
|
||||||
{
|
{
|
||||||
irremovableAfflictions.TryAdd(bloodlossAffliction = new Affliction(AfflictionPrefab.Bloodloss, 0.0f), 0);
|
irremovableAfflictions.Add(bloodlossAffliction = new Affliction(AfflictionPrefab.Bloodloss, 0.0f));
|
||||||
irremovableAfflictions.TryAdd(stunAffliction = new Affliction(AfflictionPrefab.Stun, 0.0f), 0);
|
irremovableAfflictions.Add(stunAffliction = new Affliction(AfflictionPrefab.Stun, 0.0f));
|
||||||
irremovableAfflictions.TryAdd(pressureAffliction = new Affliction(AfflictionPrefab.Pressure, 0.0f), 0);
|
irremovableAfflictions.Add(pressureAffliction = new Affliction(AfflictionPrefab.Pressure, 0.0f));
|
||||||
irremovableAfflictions.TryAdd(oxygenLowAffliction = new Affliction(AfflictionPrefab.OxygenLow, 0.0f), 0);
|
irremovableAfflictions.Add(oxygenLowAffliction = new Affliction(AfflictionPrefab.OxygenLow, 0.0f));
|
||||||
foreach (Affliction affliction in irremovableAfflictions.Keys)
|
foreach (Affliction affliction in irremovableAfflictions)
|
||||||
{
|
{
|
||||||
afflictions.TryAdd(affliction, null);
|
afflictions.Add(affliction, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,7 +338,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public IReadOnlyCollection<Affliction> GetAllAfflictions()
|
public IReadOnlyCollection<Affliction> GetAllAfflictions()
|
||||||
{
|
{
|
||||||
return afflictions.Keys.ToList();
|
return afflictions.Keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Affliction> GetAllAfflictions(Func<Affliction, bool> limbHealthFilter)
|
public IEnumerable<Affliction> GetAllAfflictions(Func<Affliction, bool> limbHealthFilter)
|
||||||
@@ -502,18 +503,19 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float GetResistance(AfflictionPrefab afflictionPrefab, LimbType limbType)
|
public float GetResistance(AfflictionPrefab afflictionPrefab, LimbType limbType)
|
||||||
{
|
{
|
||||||
// ConcurrentDictionary is thread-safe, no lock needed
|
lock (afflictions) {
|
||||||
// This is a % resistance (0 to 1.0)
|
// This is a % resistance (0 to 1.0)
|
||||||
float resistance = 0.0f;
|
float resistance = 0.0f;
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
{
|
{
|
||||||
var affliction = kvp.Key;
|
var affliction = kvp.Key;
|
||||||
resistance += affliction.GetResistance(afflictionPrefab.Identifier, limbType);
|
resistance += affliction.GetResistance(afflictionPrefab.Identifier, limbType);
|
||||||
|
}
|
||||||
|
// This is a multiplier, ie. 0.0 = 100% resistance and 1.0 = 0% resistance
|
||||||
|
float abilityResistanceMultiplier = Character.GetAbilityResistance(afflictionPrefab);
|
||||||
|
// The returned value is calculated to be a % resistance again
|
||||||
|
return 1 - ((1 - resistance) * abilityResistanceMultiplier);
|
||||||
}
|
}
|
||||||
// This is a multiplier, ie. 0.0 = 100% resistance and 1.0 = 0% resistance
|
|
||||||
float abilityResistanceMultiplier = Character.GetAbilityResistance(afflictionPrefab);
|
|
||||||
// The returned value is calculated to be a % resistance again
|
|
||||||
return 1 - ((1 - resistance) * abilityResistanceMultiplier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetStatValue(StatTypes statType)
|
public float GetStatValue(StatTypes statType)
|
||||||
@@ -537,25 +539,20 @@ namespace Barotrauma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-static to avoid concurrent modification in parallel item updates
|
private readonly List<Affliction> matchingAfflictions = new List<Affliction>();
|
||||||
[ThreadStatic]
|
|
||||||
private static List<Affliction> t_matchingAfflictions;
|
|
||||||
private static List<Affliction> MatchingAfflictions => t_matchingAfflictions ??= new List<Affliction>();
|
|
||||||
|
|
||||||
public void ReduceAllAfflictionsOnAllLimbs(float amount, ActionType? treatmentAction = null)
|
public void ReduceAllAfflictionsOnAllLimbs(float amount, ActionType? treatmentAction = null)
|
||||||
{
|
{
|
||||||
var matchingAfflictions = MatchingAfflictions;
|
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
matchingAfflictions.AddRange(afflictions.Keys);
|
matchingAfflictions.AddRange(afflictions.Keys);
|
||||||
|
|
||||||
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction);
|
ReduceMatchingAfflictions(amount, treatmentAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
public void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
||||||
{
|
{
|
||||||
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
||||||
|
|
||||||
var matchingAfflictions = MatchingAfflictions;
|
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
foreach (var affliction in afflictions)
|
foreach (var affliction in afflictions)
|
||||||
{
|
{
|
||||||
@@ -565,7 +562,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction, attacker);
|
ReduceMatchingAfflictions(amount, treatmentAction, attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<Affliction> GetAfflictionsForLimb(Limb targetLimb)
|
private IEnumerable<Affliction> GetAfflictionsForLimb(Limb targetLimb)
|
||||||
@@ -575,11 +572,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
||||||
|
|
||||||
var matchingAfflictions = MatchingAfflictions;
|
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
|
matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
|
||||||
|
|
||||||
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction);
|
ReduceMatchingAfflictions(amount, treatmentAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
||||||
@@ -587,7 +583,6 @@ namespace Barotrauma
|
|||||||
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
||||||
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
||||||
|
|
||||||
var matchingAfflictions = MatchingAfflictions;
|
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
var targetLimbHealth = limbHealths[targetLimb.HealthIndex];
|
var targetLimbHealth = limbHealths[targetLimb.HealthIndex];
|
||||||
foreach (var affliction in afflictions)
|
foreach (var affliction in afflictions)
|
||||||
@@ -598,10 +593,10 @@ namespace Barotrauma
|
|||||||
matchingAfflictions.Add(affliction.Key);
|
matchingAfflictions.Add(affliction.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction, attacker);
|
ReduceMatchingAfflictions(amount, treatmentAction, attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReduceMatchingAfflictions(List<Affliction> matchingAfflictions, float amount, ActionType? treatmentAction, Character attacker = null)
|
private void ReduceMatchingAfflictions(float amount, ActionType? treatmentAction, Character attacker = null)
|
||||||
{
|
{
|
||||||
if (matchingAfflictions.Count == 0) { return; }
|
if (matchingAfflictions.Count == 0) { return; }
|
||||||
|
|
||||||
@@ -688,19 +683,12 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-static to avoid concurrent modification when multiple characters are updated in parallel
|
private readonly static List<Affliction> afflictionsToRemove = new List<Affliction>();
|
||||||
[ThreadStatic]
|
private readonly static List<KeyValuePair<Affliction, LimbHealth>> afflictionsToUpdate = new List<KeyValuePair<Affliction, LimbHealth>>();
|
||||||
private static List<Affliction> t_afflictionsToRemove;
|
|
||||||
[ThreadStatic]
|
|
||||||
private static List<KeyValuePair<Affliction, LimbHealth>> t_afflictionsToUpdate;
|
|
||||||
private static List<Affliction> AfflictionsToRemove => t_afflictionsToRemove ??= new List<Affliction>();
|
|
||||||
private static List<KeyValuePair<Affliction, LimbHealth>> AfflictionsToUpdate => t_afflictionsToUpdate ??= new List<KeyValuePair<Affliction, LimbHealth>>();
|
|
||||||
|
|
||||||
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
|
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
|
||||||
{
|
{
|
||||||
if (Unkillable || Character.GodMode) { return; }
|
if (Unkillable || Character.GodMode) { return; }
|
||||||
|
|
||||||
var afflictionsToRemove = AfflictionsToRemove;
|
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
||||||
a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType ||
|
a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType ||
|
||||||
@@ -708,14 +696,14 @@ namespace Barotrauma
|
|||||||
a.Prefab.AfflictionType == AfflictionPrefab.Bleeding.AfflictionType));
|
a.Prefab.AfflictionType == AfflictionPrefab.Bleeding.AfflictionType));
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
{
|
{
|
||||||
afflictions.TryRemove(affliction, out _);
|
afflictions.Remove(affliction);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (LimbHealth limbHealth in limbHealths)
|
foreach (LimbHealth limbHealth in limbHealths)
|
||||||
{
|
{
|
||||||
if (damageAmount > 0.0f) { afflictions.TryAdd(AfflictionPrefab.InternalDamage.Instantiate(damageAmount), limbHealth); }
|
if (damageAmount > 0.0f) { afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damageAmount), limbHealth); }
|
||||||
if (bleedingDamageAmount > 0.0f && DoesBleed) { afflictions.TryAdd(AfflictionPrefab.Bleeding.Instantiate(bleedingDamageAmount), limbHealth); }
|
if (bleedingDamageAmount > 0.0f && DoesBleed) { afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamageAmount), limbHealth); }
|
||||||
if (burnDamageAmount > 0.0f) { afflictions.TryAdd(AfflictionPrefab.Burn.Instantiate(burnDamageAmount), limbHealth); }
|
if (burnDamageAmount > 0.0f) { afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamageAmount), limbHealth); }
|
||||||
}
|
}
|
||||||
|
|
||||||
RecalculateVitality();
|
RecalculateVitality();
|
||||||
@@ -751,28 +739,26 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void RemoveAfflictions(Func<Affliction, bool> predicate)
|
public void RemoveAfflictions(Func<Affliction, bool> predicate)
|
||||||
{
|
{
|
||||||
var afflictionsToRemove = AfflictionsToRemove;
|
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(affliction => predicate(affliction)));
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(affliction => predicate(affliction)));
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
{
|
{
|
||||||
afflictions.TryRemove(affliction, out _);
|
afflictions.Remove(affliction);
|
||||||
}
|
}
|
||||||
CalculateVitality();
|
CalculateVitality();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveAllAfflictions()
|
public void RemoveAllAfflictions()
|
||||||
{
|
{
|
||||||
var afflictionsToRemove = AfflictionsToRemove;
|
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.ContainsKey(a)));
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.Contains(a)));
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
{
|
{
|
||||||
//set strength to 0 in case the affliction needs to react to becoming inactive
|
//set strength to 0 in case the affliction needs to react to becoming inactive
|
||||||
affliction.Strength = 0.0f;
|
affliction.Strength = 0.0f;
|
||||||
afflictions.TryRemove(affliction, out _);
|
afflictions.Remove(affliction);
|
||||||
}
|
}
|
||||||
foreach (Affliction affliction in irremovableAfflictions.Keys)
|
foreach (Affliction affliction in irremovableAfflictions)
|
||||||
{
|
{
|
||||||
affliction.Strength = 0.0f;
|
affliction.Strength = 0.0f;
|
||||||
}
|
}
|
||||||
@@ -781,18 +767,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void RemoveNegativeAfflictions()
|
public void RemoveNegativeAfflictions()
|
||||||
{
|
{
|
||||||
var afflictionsToRemove = AfflictionsToRemove;
|
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
||||||
!irremovableAfflictions.ContainsKey(a) &&
|
!irremovableAfflictions.Contains(a) &&
|
||||||
!a.Prefab.IsBuff &&
|
!a.Prefab.IsBuff &&
|
||||||
a.Prefab.AfflictionType != "geneticmaterialbuff" &&
|
a.Prefab.AfflictionType != "geneticmaterialbuff" &&
|
||||||
a.Prefab.AfflictionType != "geneticmaterialdebuff"));
|
a.Prefab.AfflictionType != "geneticmaterialdebuff"));
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
{
|
{
|
||||||
afflictions.TryRemove(affliction, out _);
|
afflictions.Remove(affliction);
|
||||||
}
|
}
|
||||||
foreach (Affliction affliction in irremovableAfflictions.Keys)
|
foreach (Affliction affliction in irremovableAfflictions)
|
||||||
{
|
{
|
||||||
affliction.Strength = 0.0f;
|
affliction.Strength = 0.0f;
|
||||||
}
|
}
|
||||||
@@ -884,7 +869,7 @@ namespace Barotrauma
|
|||||||
var copyAffliction = newAffliction.Prefab.Instantiate(
|
var copyAffliction = newAffliction.Prefab.Instantiate(
|
||||||
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab, limbType))),
|
Math.Min(newAffliction.Prefab.MaxStrength, newAffliction.Strength * (100.0f / MaxVitality) * (1f - GetResistance(newAffliction.Prefab, limbType))),
|
||||||
newAffliction.Source);
|
newAffliction.Source);
|
||||||
afflictions.TryAdd(copyAffliction, limbHealth);
|
afflictions.Add(copyAffliction, limbHealth);
|
||||||
AchievementManager.OnAfflictionReceived(copyAffliction, Character);
|
AchievementManager.OnAfflictionReceived(copyAffliction, Character);
|
||||||
MedicalClinic.OnAfflictionCountChanged(Character);
|
MedicalClinic.OnAfflictionCountChanged(Character);
|
||||||
|
|
||||||
@@ -921,8 +906,6 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!Character.GodMode)
|
if (!Character.GodMode)
|
||||||
{
|
{
|
||||||
var afflictionsToRemove = AfflictionsToRemove;
|
|
||||||
var afflictionsToUpdate = AfflictionsToUpdate;
|
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToUpdate.Clear();
|
afflictionsToUpdate.Clear();
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
@@ -931,7 +914,7 @@ namespace Barotrauma
|
|||||||
if (affliction.Strength <= 0.0f)
|
if (affliction.Strength <= 0.0f)
|
||||||
{
|
{
|
||||||
AchievementManager.OnAfflictionRemoved(affliction, Character);
|
AchievementManager.OnAfflictionRemoved(affliction, Character);
|
||||||
if (!irremovableAfflictions.ContainsKey(affliction)) { afflictionsToRemove.Add(affliction); }
|
if (!irremovableAfflictions.Contains(affliction)) { afflictionsToRemove.Add(affliction); }
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (affliction.Prefab.Duration > 0.0f)
|
if (affliction.Prefab.Duration > 0.0f)
|
||||||
@@ -969,7 +952,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
{
|
{
|
||||||
afflictions.TryRemove(affliction, out _);
|
afflictions.Remove(affliction);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (afflictionsToRemove.Count is not 0)
|
if (afflictionsToRemove.Count is not 0)
|
||||||
@@ -1217,14 +1200,9 @@ namespace Barotrauma
|
|||||||
return (causeOfDeath, strongestAffliction);
|
return (causeOfDeath, strongestAffliction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-static to avoid concurrent modification in parallel item updates
|
private readonly List<Affliction> allAfflictions = new List<Affliction>();
|
||||||
[ThreadStatic]
|
|
||||||
private static List<Affliction> t_allAfflictions;
|
|
||||||
private static List<Affliction> AllAfflictionsList => t_allAfflictions ??= new List<Affliction>();
|
|
||||||
|
|
||||||
private IEnumerable<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
private IEnumerable<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
||||||
{
|
{
|
||||||
var allAfflictions = AllAfflictionsList;
|
|
||||||
allAfflictions.Clear();
|
allAfflictions.Clear();
|
||||||
if (!mergeSameAfflictions)
|
if (!mergeSameAfflictions)
|
||||||
{
|
{
|
||||||
@@ -1407,17 +1385,10 @@ namespace Barotrauma
|
|||||||
return MathHelper.Clamp(strength, 0.0f, affliction.Prefab.MaxStrength);
|
return MathHelper.Clamp(strength, 0.0f, affliction.Prefab.MaxStrength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-static to avoid concurrent modification in parallel updates
|
private readonly List<Affliction> activeAfflictions = new List<Affliction>();
|
||||||
[ThreadStatic]
|
private readonly List<(LimbHealth limbHealth, Affliction affliction)> limbAfflictions = new List<(LimbHealth limbHealth, Affliction affliction)>();
|
||||||
private static List<Affliction> t_activeAfflictions;
|
|
||||||
[ThreadStatic]
|
|
||||||
private static List<(LimbHealth limbHealth, Affliction affliction)> t_limbAfflictions;
|
|
||||||
private static List<Affliction> ActiveAfflictionsList => t_activeAfflictions ??= new List<Affliction>();
|
|
||||||
private static List<(LimbHealth limbHealth, Affliction affliction)> LimbAfflictionsList => t_limbAfflictions ??= new List<(LimbHealth limbHealth, Affliction affliction)>();
|
|
||||||
|
|
||||||
public void ServerWrite(IWriteMessage msg)
|
public void ServerWrite(IWriteMessage msg)
|
||||||
{
|
{
|
||||||
var activeAfflictions = ActiveAfflictionsList;
|
|
||||||
activeAfflictions.Clear();
|
activeAfflictions.Clear();
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
{
|
{
|
||||||
@@ -1443,7 +1414,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var limbAfflictions = LimbAfflictionsList;
|
|
||||||
limbAfflictions.Clear();
|
limbAfflictions.Clear();
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
{
|
{
|
||||||
@@ -1473,9 +1443,8 @@ namespace Barotrauma
|
|||||||
public void Remove()
|
public void Remove()
|
||||||
{
|
{
|
||||||
RemoveProjSpecific();
|
RemoveProjSpecific();
|
||||||
// Clear thread-static lists to help with garbage collection
|
afflictionsToRemove.Clear();
|
||||||
AfflictionsToRemove.Clear();
|
afflictionsToUpdate.Clear();
|
||||||
AfflictionsToUpdate.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void RemoveProjSpecific();
|
partial void RemoveProjSpecific();
|
||||||
@@ -1550,14 +1519,14 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (afflictionPredicate != null && !afflictionPredicate.Invoke(afflictionPrefab)) { return; }
|
if (afflictionPredicate != null && !afflictionPredicate.Invoke(afflictionPrefab)) { return; }
|
||||||
float strength = afflictionElement.GetAttributeFloat("strength", 0.0f);
|
float strength = afflictionElement.GetAttributeFloat("strength", 0.0f);
|
||||||
var irremovableAffliction = irremovableAfflictions.Keys.FirstOrDefault(a => a.Prefab == afflictionPrefab);
|
var irremovableAffliction = irremovableAfflictions.FirstOrDefault(a => a.Prefab == afflictionPrefab);
|
||||||
if (irremovableAffliction != null)
|
if (irremovableAffliction != null)
|
||||||
{
|
{
|
||||||
irremovableAffliction.Strength = strength;
|
irremovableAffliction.Strength = strength;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
afflictions.TryAdd(afflictionPrefab.Instantiate(strength), limbHealth);
|
afflictions.Add(afflictionPrefab.Instantiate(strength), limbHealth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -797,14 +797,16 @@ namespace Barotrauma
|
|||||||
return AddDamage(simPosition, afflictions, playSound);
|
return AddDamage(simPosition, afflictions, playSound);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-safe: using local variables instead of instance fields to avoid concurrent modification
|
private readonly List<DamageModifier> appliedDamageModifiers = new List<DamageModifier>();
|
||||||
|
private readonly List<DamageModifier> tempModifiers = new List<DamageModifier>();
|
||||||
|
private readonly List<Affliction> afflictionsCopy = new List<Affliction>();
|
||||||
public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f, Character attacker = null)
|
public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f, Character attacker = null)
|
||||||
{
|
{
|
||||||
var appliedDamageModifiers = new List<DamageModifier>();
|
appliedDamageModifiers.Clear();
|
||||||
var afflictionsCopy = new List<Affliction>();
|
afflictionsCopy.Clear();
|
||||||
foreach (var affliction in afflictions)
|
foreach (var affliction in afflictions)
|
||||||
{
|
{
|
||||||
var tempModifiers = new List<DamageModifier>();
|
tempModifiers.Clear();
|
||||||
var newAffliction = affliction;
|
var newAffliction = affliction;
|
||||||
float random = Rand.Value(Rand.RandSync.Unsynced);
|
float random = Rand.Value(Rand.RandSync.Unsynced);
|
||||||
bool foundMatchingModifier = false;
|
bool foundMatchingModifier = false;
|
||||||
@@ -1020,18 +1022,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime);
|
partial void UpdateProjSpecific(float deltaTime);
|
||||||
|
|
||||||
// Thread-static to avoid concurrent modification in parallel item updates
|
private readonly List<Body> contactBodies = new List<Body>();
|
||||||
[ThreadStatic]
|
|
||||||
private static List<Body> t_contactBodies;
|
|
||||||
private static List<Body> ContactBodies => t_contactBodies ??= new List<Body>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
|
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
|
||||||
{
|
{
|
||||||
attackResult = default;
|
attackResult = default;
|
||||||
var contactBodies = ContactBodies;
|
|
||||||
Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition;
|
Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition;
|
||||||
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
|
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
|
||||||
bool wasRunning = attack.IsRunning;
|
bool wasRunning = attack.IsRunning;
|
||||||
@@ -1290,11 +1287,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-static to avoid concurrent modification in parallel item updates
|
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
||||||
[ThreadStatic]
|
|
||||||
private static List<ISerializableEntity> t_statusEffectTargets;
|
|
||||||
private static List<ISerializableEntity> StatusEffectTargets => t_statusEffectTargets ??= new List<ISerializableEntity>();
|
|
||||||
|
|
||||||
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
||||||
{
|
{
|
||||||
if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
|
if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
|
||||||
@@ -1317,7 +1310,6 @@ namespace Barotrauma
|
|||||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||||
{
|
{
|
||||||
var targets = StatusEffectTargets;
|
|
||||||
targets.Clear();
|
targets.Clear();
|
||||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||||
statusEffect.Apply(actionType, deltaTime, character, targets);
|
statusEffect.Apply(actionType, deltaTime, character, targets);
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Barotrauma.IO;
|
using Barotrauma.IO;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
|
|
||||||
@@ -119,9 +117,8 @@ namespace Barotrauma
|
|||||||
public virtual AnimationType AnimationType { get; protected set; }
|
public virtual AnimationType AnimationType { get; protected set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The cached animations of all the characters that have been loaded.
|
/// The cached animations of all the characters that have been loaded.
|
||||||
/// Thread-safe cache using ConcurrentDictionary.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly ConcurrentDictionary<Identifier, ConcurrentDictionary<string, AnimationParams>> allAnimations = new ConcurrentDictionary<Identifier, ConcurrentDictionary<string, AnimationParams>>();
|
private static readonly Dictionary<Identifier, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<Identifier, Dictionary<string, AnimationParams>>();
|
||||||
|
|
||||||
[Header("Movement")]
|
[Header("Movement")]
|
||||||
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = Ragdoll.MAX_SPEED, ValueStep = 0.1f)]
|
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = Ragdoll.MAX_SPEED, ValueStep = 0.1f)]
|
||||||
@@ -247,9 +244,7 @@ namespace Barotrauma
|
|||||||
return GetAnimParams<T>(speciesName, animSpecies, fallbackSpecies: character.Prefab.GetBaseCharacterSpeciesName(speciesName), animType, file, throwErrors);
|
return GetAnimParams<T>(speciesName, animSpecies, fallbackSpecies: character.Prefab.GetBaseCharacterSpeciesName(speciesName), animType, file, throwErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThreadLocal for thread-safe error message collection during animation loading
|
private static readonly List<string> errorMessages = new List<string>();
|
||||||
private static readonly ThreadLocal<List<string>> errorMessagesLocal = new ThreadLocal<List<string>>(() => new List<string>());
|
|
||||||
private static List<string> errorMessages => errorMessagesLocal.Value;
|
|
||||||
|
|
||||||
private static T GetAnimParams<T>(Identifier speciesName, Identifier animSpecies, Identifier fallbackSpecies, AnimationType animType, Either<string, ContentPath> file, bool throwErrors = true) where T : AnimationParams, new()
|
private static T GetAnimParams<T>(Identifier speciesName, Identifier animSpecies, Identifier fallbackSpecies, AnimationType animType, Either<string, ContentPath> file, bool throwErrors = true) where T : AnimationParams, new()
|
||||||
{
|
{
|
||||||
@@ -267,7 +262,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
ContentPackage contentPackage = contentPath?.ContentPackage ?? CharacterPrefab.FindBySpeciesName(speciesName)?.ContentPackage;
|
ContentPackage contentPackage = contentPath?.ContentPackage ?? CharacterPrefab.FindBySpeciesName(speciesName)?.ContentPackage;
|
||||||
Debug.Assert(contentPackage != null);
|
Debug.Assert(contentPackage != null);
|
||||||
var animations = allAnimations.GetOrAdd(speciesName, _ => new ConcurrentDictionary<string, AnimationParams>());
|
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> animations))
|
||||||
|
{
|
||||||
|
animations = new Dictionary<string, AnimationParams>();
|
||||||
|
allAnimations.Add(speciesName, animations);
|
||||||
|
}
|
||||||
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(animSpecies, animType);
|
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(animSpecies, animType);
|
||||||
if (animations.TryGetValue(key, out AnimationParams anim) && anim.AnimationType == animType)
|
if (animations.TryGetValue(key, out AnimationParams anim) && anim.AnimationType == animType)
|
||||||
{
|
{
|
||||||
@@ -419,12 +418,16 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
throw new Exception("Cannot create an animation file of type " + animationType);
|
throw new Exception("Cannot create an animation file of type " + animationType);
|
||||||
}
|
}
|
||||||
var anims = allAnimations.GetOrAdd(speciesName, _ => new ConcurrentDictionary<string, AnimationParams>());
|
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> anims))
|
||||||
|
{
|
||||||
|
anims = new Dictionary<string, AnimationParams>();
|
||||||
|
allAnimations.Add(speciesName, anims);
|
||||||
|
}
|
||||||
string fileName = IO.Path.GetFileNameWithoutExtension(fullPath);
|
string fileName = IO.Path.GetFileNameWithoutExtension(fullPath);
|
||||||
if (anims.ContainsKey(fileName))
|
if (anims.ContainsKey(fileName))
|
||||||
{
|
{
|
||||||
DebugConsole.NewMessage($"[AnimationParams] Removing the old animation of type {animationType}.", Color.Red);
|
DebugConsole.NewMessage($"[AnimationParams] Removing the old animation of type {animationType}.", Color.Red);
|
||||||
anims.TryRemove(fileName, out _);
|
anims.Remove(fileName);
|
||||||
}
|
}
|
||||||
var instance = new T();
|
var instance = new T();
|
||||||
XElement animationElement = new XElement(GetDefaultFileName(speciesName, animationType), new XAttribute("animationtype", animationType.ToString()));
|
XElement animationElement = new XElement(GetDefaultFileName(speciesName, animationType), new XAttribute("animationtype", animationType.ToString()));
|
||||||
@@ -436,7 +439,7 @@ namespace Barotrauma
|
|||||||
instance.IsLoaded = instance.Deserialize(animationElement);
|
instance.IsLoaded = instance.Deserialize(animationElement);
|
||||||
instance.Save();
|
instance.Save();
|
||||||
instance.Load(contentPath, speciesName);
|
instance.Load(contentPath, speciesName);
|
||||||
anims.TryAdd(fileName, instance);
|
anims.Add(fileName, instance);
|
||||||
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
|
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -464,14 +467,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
// Update the key by removing and re-adding the animation.
|
// Update the key by removing and re-adding the animation.
|
||||||
string fileName = FileNameWithoutExtension;
|
string fileName = FileNameWithoutExtension;
|
||||||
if (allAnimations.TryGetValue(SpeciesName, out ConcurrentDictionary<string, AnimationParams> animations))
|
if (allAnimations.TryGetValue(SpeciesName, out Dictionary<string, AnimationParams> animations))
|
||||||
{
|
{
|
||||||
animations.TryRemove(fileName, out _);
|
animations.Remove(fileName);
|
||||||
}
|
}
|
||||||
base.UpdatePath(newPath);
|
base.UpdatePath(newPath);
|
||||||
if (animations != null)
|
if (animations != null)
|
||||||
{
|
{
|
||||||
animations.TryAdd(fileName, this);
|
if (!animations.ContainsKey(fileName))
|
||||||
|
{
|
||||||
|
animations.Add(fileName, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
@@ -125,9 +124,8 @@ namespace Barotrauma
|
|||||||
/// key1: Species name
|
/// key1: Species name
|
||||||
/// key2: File path
|
/// key2: File path
|
||||||
/// value: Ragdoll parameters
|
/// value: Ragdoll parameters
|
||||||
/// Thread-safe cache using ConcurrentDictionary.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly ConcurrentDictionary<Identifier, ConcurrentDictionary<string, RagdollParams>> allRagdolls = new ConcurrentDictionary<Identifier, ConcurrentDictionary<string, RagdollParams>>();
|
private static readonly Dictionary<Identifier, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<Identifier, Dictionary<string, RagdollParams>>();
|
||||||
|
|
||||||
public List<ColliderParams> Colliders { get; private set; } = new List<ColliderParams>();
|
public List<ColliderParams> Colliders { get; private set; } = new List<ColliderParams>();
|
||||||
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
|
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
|
||||||
@@ -224,7 +222,11 @@ namespace Barotrauma
|
|||||||
Debug.Assert(!fileName.IsNullOrWhiteSpace() || !contentPath.IsNullOrWhiteSpace());
|
Debug.Assert(!fileName.IsNullOrWhiteSpace() || !contentPath.IsNullOrWhiteSpace());
|
||||||
}
|
}
|
||||||
Debug.Assert(contentPackage != null);
|
Debug.Assert(contentPackage != null);
|
||||||
var ragdolls = allRagdolls.GetOrAdd(speciesName, _ => new ConcurrentDictionary<string, RagdollParams>());
|
if (!allRagdolls.TryGetValue(speciesName, out Dictionary<string, RagdollParams> ragdolls))
|
||||||
|
{
|
||||||
|
ragdolls = new Dictionary<string, RagdollParams>();
|
||||||
|
allRagdolls.Add(speciesName, ragdolls);
|
||||||
|
}
|
||||||
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(ragdollSpecies);
|
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(ragdollSpecies);
|
||||||
if (ragdolls.TryGetValue(key, out RagdollParams ragdoll))
|
if (ragdolls.TryGetValue(key, out RagdollParams ragdoll))
|
||||||
{
|
{
|
||||||
@@ -329,10 +331,10 @@ namespace Barotrauma
|
|||||||
if (allRagdolls.ContainsKey(speciesName))
|
if (allRagdolls.ContainsKey(speciesName))
|
||||||
{
|
{
|
||||||
DebugConsole.NewMessage($"[RagdollParams] Removing the old ragdolls from {speciesName}.", Color.Red);
|
DebugConsole.NewMessage($"[RagdollParams] Removing the old ragdolls from {speciesName}.", Color.Red);
|
||||||
allRagdolls.TryRemove(speciesName, out _);
|
allRagdolls.Remove(speciesName);
|
||||||
}
|
}
|
||||||
var ragdolls = new ConcurrentDictionary<string, RagdollParams>();
|
var ragdolls = new Dictionary<string, RagdollParams>();
|
||||||
allRagdolls.TryAdd(speciesName, ragdolls);
|
allRagdolls.Add(speciesName, ragdolls);
|
||||||
var instance = new T
|
var instance = new T
|
||||||
{
|
{
|
||||||
doc = new XDocument(mainElement)
|
doc = new XDocument(mainElement)
|
||||||
@@ -343,7 +345,7 @@ namespace Barotrauma
|
|||||||
instance.IsLoaded = instance.Deserialize(mainElement);
|
instance.IsLoaded = instance.Deserialize(mainElement);
|
||||||
instance.Save();
|
instance.Save();
|
||||||
instance.Load(contentPath, speciesName);
|
instance.Load(contentPath, speciesName);
|
||||||
ragdolls.TryAdd(instance.FileNameWithoutExtension, instance);
|
ragdolls.Add(instance.FileNameWithoutExtension, instance);
|
||||||
DebugConsole.NewMessage("[RagdollParams] New default ragdoll params successfully created at " + fullPath, Color.NavajoWhite);
|
DebugConsole.NewMessage("[RagdollParams] New default ragdoll params successfully created at " + fullPath, Color.NavajoWhite);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -360,14 +362,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
// Update the key by removing and re-adding the ragdoll.
|
// Update the key by removing and re-adding the ragdoll.
|
||||||
string fileName = FileNameWithoutExtension;
|
string fileName = FileNameWithoutExtension;
|
||||||
if (allRagdolls.TryGetValue(SpeciesName, out ConcurrentDictionary<string, RagdollParams> ragdolls))
|
if (allRagdolls.TryGetValue(SpeciesName, out Dictionary<string, RagdollParams> ragdolls))
|
||||||
{
|
{
|
||||||
ragdolls.TryRemove(fileName, out _);
|
ragdolls.Remove(fileName);
|
||||||
}
|
}
|
||||||
base.UpdatePath(fullPath);
|
base.UpdatePath(fullPath);
|
||||||
if (ragdolls != null)
|
if (ragdolls != null)
|
||||||
{
|
{
|
||||||
ragdolls.TryAdd(fileName, this);
|
if (!ragdolls.ContainsKey(fileName))
|
||||||
|
{
|
||||||
|
ragdolls.Add(fileName, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1479,4 +1484,4 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
using Barotrauma.Abilities;
|
using Barotrauma.Abilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -74,9 +72,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThreadLocal for thread-safe talent checking
|
private static readonly HashSet<Identifier> checkedNonStackableTalents = new();
|
||||||
private static readonly ThreadLocal<HashSet<Identifier>> checkedNonStackableTalentsLocal = new ThreadLocal<HashSet<Identifier>>(() => new HashSet<Identifier>());
|
|
||||||
private static HashSet<Identifier> checkedNonStackableTalents => checkedNonStackableTalentsLocal.Value;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks talents for a given AbilityObject taking into account non-stackable talents.
|
/// Checks talents for a given AbilityObject taking into account non-stackable talents.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -21,7 +21,7 @@ namespace Barotrauma
|
|||||||
var npcConversationCollection = new NPCConversationCollection(this, mainElement);
|
var npcConversationCollection = new NPCConversationCollection(this, mainElement);
|
||||||
if (!NPCConversationCollection.Collections.ContainsKey(npcConversationCollection.Language))
|
if (!NPCConversationCollection.Collections.ContainsKey(npcConversationCollection.Language))
|
||||||
{
|
{
|
||||||
NPCConversationCollection.Collections.TryAdd(npcConversationCollection.Language, new PrefabCollection<NPCConversationCollection>());
|
NPCConversationCollection.Collections.Add(npcConversationCollection.Language, new PrefabCollection<NPCConversationCollection>());
|
||||||
}
|
}
|
||||||
NPCConversationCollection.Collections[npcConversationCollection.Language].Add(npcConversationCollection, allowOverriding);
|
NPCConversationCollection.Collections[npcConversationCollection.Language].Add(npcConversationCollection, allowOverriding);
|
||||||
}
|
}
|
||||||
@@ -42,4 +42,4 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1478,7 +1478,7 @@ namespace Barotrauma
|
|||||||
newItemName = args[2];
|
newItemName = args[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldItem = Item.ItemList.Where(it => it.Name == args[0]).ElementAtOrDefault(itemIndex);
|
var oldItem = Item.ItemList.FindAll(it => it.Name == args[0]).ElementAtOrDefault(itemIndex);
|
||||||
if (oldItem == null)
|
if (oldItem == null)
|
||||||
{
|
{
|
||||||
ThrowError($"Could not find an item with the name {args[0]} (index {itemIndex}).");
|
ThrowError($"Could not find an item with the name {args[0]} (index {itemIndex}).");
|
||||||
@@ -1852,7 +1852,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
commands.Add(new Command("power", "power: Immediately powers up the submarine's nuclear reactor.", (string[] args) =>
|
commands.Add(new Command("power", "power: Immediately powers up the submarine's nuclear reactor.", (string[] args) =>
|
||||||
{
|
{
|
||||||
Item reactorItem = Item.ItemList.FirstOrDefault(i => i.GetComponent<Reactor>() != null);
|
Item reactorItem = Item.ItemList.Find(i => i.GetComponent<Reactor>() != null);
|
||||||
if (reactorItem == null) { return; }
|
if (reactorItem == null) { return; }
|
||||||
|
|
||||||
var reactor = reactorItem.GetComponent<Reactor>();
|
var reactor = reactorItem.GetComponent<Reactor>();
|
||||||
@@ -3225,7 +3225,7 @@ namespace Barotrauma
|
|||||||
if (args.Length > spawnLocationIndex + 1)
|
if (args.Length > spawnLocationIndex + 1)
|
||||||
{
|
{
|
||||||
if (!int.TryParse(args[spawnLocationIndex + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; }
|
if (!int.TryParse(args[spawnLocationIndex + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; }
|
||||||
amount = Math.Min(amount, 100000);
|
amount = Math.Min(amount, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Length > spawnLocationIndex + 2)
|
if (args.Length > spawnLocationIndex + 2)
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ namespace Barotrauma
|
|||||||
MissionAction.ResetMissionsUnlockedThisRound();
|
MissionAction.ResetMissionsUnlockedThisRound();
|
||||||
UnlockPathAction.ResetPathsUnlockedThisRound();
|
UnlockPathAction.ResetPathsUnlockedThisRound();
|
||||||
#endif
|
#endif
|
||||||
pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false);
|
pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||||
totalPathLength = 0.0f;
|
totalPathLength = 0.0f;
|
||||||
if (level != null)
|
if (level != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override void InitEventSpecific(EventSet parentSet)
|
protected override void InitEventSpecific(EventSet parentSet)
|
||||||
{
|
{
|
||||||
var matchingItems = Item.ItemList.Where(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier)).ToList();
|
var matchingItems = Item.ItemList.FindAll(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier));
|
||||||
int itemAmount = Rand.Range(minItemAmount, maxItemAmount, Rand.RandSync.ServerAndClient);
|
int itemAmount = Rand.Range(minItemAmount, maxItemAmount, Rand.RandSync.ServerAndClient);
|
||||||
for (int i = 0; i < itemAmount; i++)
|
for (int i = 0; i < itemAmount; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!itemTag.IsEmpty)
|
if (!itemTag.IsEmpty)
|
||||||
{
|
{
|
||||||
var itemsToDestroy = Item.ItemList.Where(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag)).ToList();
|
var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
|
||||||
if (!itemsToDestroy.Any())
|
if (!itemsToDestroy.Any())
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".",
|
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".",
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ namespace Barotrauma
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
destructibleItems.Clear();
|
destructibleItems.Clear();
|
||||||
destructibleItems.AddRange(Item.ItemList.Where(it => it.HasTag(destructibleItemTag)));
|
destructibleItems.AddRange(Item.ItemList.FindAll(it => it.HasTag(destructibleItemTag)));
|
||||||
if (destructibleItems.None())
|
if (destructibleItems.None())
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find any destructible items with the tag \"{spawnPointTag}\".",
|
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find any destructible items with the tag \"{spawnPointTag}\".",
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!IsClient)
|
if (!IsClient)
|
||||||
{
|
{
|
||||||
PathFinder pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false);
|
PathFinder pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||||
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos));
|
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos));
|
||||||
if (!path.Unreachable)
|
if (!path.Unreachable)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (var stackedItem in item.GetStackedItems())
|
foreach (var stackedItem in item.GetStackedItems())
|
||||||
{
|
{
|
||||||
Item.MarkForDeconstruction(stackedItem);
|
Item.DeconstructItems.Add(stackedItem);
|
||||||
}
|
}
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
HintManager.OnItemMarkedForDeconstruction(order.OrderGiver);
|
HintManager.OnItemMarkedForDeconstruction(order.OrderGiver);
|
||||||
@@ -138,7 +138,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (var stackedItem in item.GetStackedItems())
|
foreach (var stackedItem in item.GetStackedItems())
|
||||||
{
|
{
|
||||||
Item.UnmarkForDeconstruction(stackedItem);
|
Item.DeconstructItems.Remove(stackedItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1094,7 +1094,7 @@ namespace Barotrauma
|
|||||||
#endif
|
#endif
|
||||||
//Clear the grids to allow for garbage collection
|
//Clear the grids to allow for garbage collection
|
||||||
Powered.Grids.Clear();
|
Powered.Grids.Clear();
|
||||||
Powered.ClearChangedConnections();
|
Powered.ChangedConnections.Clear();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1153,7 +1153,6 @@ namespace Barotrauma
|
|||||||
EventManager?.EndRound();
|
EventManager?.EndRound();
|
||||||
StatusEffect.StopAll();
|
StatusEffect.StopAll();
|
||||||
AfflictionPrefab.ClearAllEffects();
|
AfflictionPrefab.ClearAllEffects();
|
||||||
PhysicsBodyQueue.Clear();
|
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using FarseerPhysics.Dynamics;
|
|||||||
using FarseerPhysics.Dynamics.Joints;
|
using FarseerPhysics.Dynamics.Joints;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
@@ -25,8 +24,11 @@ namespace Barotrauma.Items.Components
|
|||||||
Right
|
Right
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<DockingPort, byte> _dockingPortDict = new ConcurrentDictionary<DockingPort, byte>();
|
private static readonly List<DockingPort> list = new List<DockingPort>();
|
||||||
public static IEnumerable<DockingPort> List => _dockingPortDict.Keys;
|
public static IEnumerable<DockingPort> List
|
||||||
|
{
|
||||||
|
get { return list; }
|
||||||
|
}
|
||||||
|
|
||||||
private Sprite overlaySprite;
|
private Sprite overlaySprite;
|
||||||
private float dockingState;
|
private float dockingState;
|
||||||
@@ -166,7 +168,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
IsActive = true;
|
IsActive = true;
|
||||||
|
|
||||||
_dockingPortDict.TryAdd(this, 0);
|
list.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void FlipX(bool relativeToSub)
|
public override void FlipX(bool relativeToSub)
|
||||||
@@ -198,7 +200,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
float closestDist = float.MaxValue;
|
float closestDist = float.MaxValue;
|
||||||
DockingPort closestPort = null;
|
DockingPort closestPort = null;
|
||||||
foreach (DockingPort port in List)
|
foreach (DockingPort port in list)
|
||||||
{
|
{
|
||||||
if (port == this || port.item.Submarine == item.Submarine || port.IsHorizontal != IsHorizontal) { continue; }
|
if (port == this || port.item.Submarine == item.Submarine || port.IsHorizontal != IsHorizontal) { continue; }
|
||||||
float xDist = Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X);
|
float xDist = Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X);
|
||||||
@@ -530,8 +532,8 @@ namespace Barotrauma.Items.Components
|
|||||||
wire.TryConnect(recipient, addNode: false);
|
wire.TryConnect(recipient, addNode: false);
|
||||||
|
|
||||||
//Flag connections to be updated
|
//Flag connections to be updated
|
||||||
Powered.MarkConnectionChanged(powerConnection);
|
Powered.ChangedConnections.Add(powerConnection);
|
||||||
Powered.MarkConnectionChanged(recipient);
|
Powered.ChangedConnections.Add(recipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateDoorBody()
|
private void CreateDoorBody()
|
||||||
@@ -1005,7 +1007,7 @@ namespace Barotrauma.Items.Components
|
|||||||
Connection powerConnection = Item.Connections.Find(c => c.IsPower);
|
Connection powerConnection = Item.Connections.Find(c => c.IsPower);
|
||||||
if (powerConnection != null)
|
if (powerConnection != null)
|
||||||
{
|
{
|
||||||
Powered.MarkConnectionChanged(powerConnection);
|
Powered.ChangedConnections.Add(powerConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doorBody != null)
|
if (doorBody != null)
|
||||||
@@ -1149,7 +1151,7 @@ namespace Barotrauma.Items.Components
|
|||||||
protected override void RemoveComponentSpecific()
|
protected override void RemoveComponentSpecific()
|
||||||
{
|
{
|
||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
_dockingPortDict.TryRemove(this, out _);
|
list.Remove(this);
|
||||||
hulls[0]?.Remove(); hulls[0] = null;
|
hulls[0]?.Remove(); hulls[0] = null;
|
||||||
hulls[1]?.Remove(); hulls[1] = null;
|
hulls[1]?.Remove(); hulls[1] = null;
|
||||||
gap?.Remove(); gap = null;
|
gap?.Remove(); gap = null;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using FarseerPhysics;
|
using FarseerPhysics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FarseerPhysics.Dynamics;
|
using FarseerPhysics.Dynamics;
|
||||||
@@ -15,9 +14,9 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
partial class Door : Pickable, IDrawableComponent, IServerSerializable
|
partial class Door : Pickable, IDrawableComponent, IServerSerializable
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<Door, byte> _doorDict = new ConcurrentDictionary<Door, byte>();
|
private static readonly HashSet<Door> doorList = new HashSet<Door>();
|
||||||
|
|
||||||
public static ICollection<Door> DoorList => _doorDict.Keys;
|
public static IReadOnlyCollection<Door> DoorList { get { return doorList; } }
|
||||||
|
|
||||||
private Gap linkedGap;
|
private Gap linkedGap;
|
||||||
private bool isOpen;
|
private bool isOpen;
|
||||||
@@ -278,7 +277,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
IsActive = true;
|
IsActive = true;
|
||||||
_doorDict.TryAdd(this, 0);
|
doorList.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnItemLoaded()
|
public override void OnItemLoaded()
|
||||||
@@ -314,21 +313,13 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||||
{
|
{
|
||||||
// Defer physics operation if in parallel context (Farseer is not thread-safe)
|
if (ignoreContacts)
|
||||||
if (Body != null)
|
|
||||||
{
|
{
|
||||||
var capturedBody = Body;
|
Body?.SetTransformIgnoreContacts(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||||
var capturedNewPos = Body.SimPosition + ConvertUnits.ToSimUnits(amount);
|
}
|
||||||
if (ignoreContacts)
|
else
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() =>
|
Body?.SetTransform(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||||
capturedBody.SetTransformIgnoreContacts(capturedNewPos, 0.0f));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() =>
|
|
||||||
capturedBody.SetTransform(capturedNewPos, 0.0f));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
UpdateConvexHulls();
|
UpdateConvexHulls();
|
||||||
@@ -678,7 +669,7 @@ namespace Barotrauma.Items.Components
|
|||||||
convexHull2?.Remove();
|
convexHull2?.Remove();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_doorDict.TryRemove(this, out _);
|
doorList.Remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckSubmarinesInDoorWay()
|
private bool CheckSubmarinesInDoorWay()
|
||||||
@@ -794,19 +785,13 @@ namespace Barotrauma.Items.Components
|
|||||||
//immediately teleport it to the correct side
|
//immediately teleport it to the correct side
|
||||||
if (Math.Sign(diff) != dir)
|
if (Math.Sign(diff) != dir)
|
||||||
{
|
{
|
||||||
// Defer physics operation if in parallel context (Farseer is not thread-safe)
|
|
||||||
var capturedBody = body;
|
|
||||||
if (IsHorizontal)
|
if (IsHorizontal)
|
||||||
{
|
{
|
||||||
Vector2 newPos = new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * doorRectSimSize.Y * 2.0f);
|
body.SetTransformIgnoreContacts(new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * doorRectSimSize.Y * 2.0f), body.Rotation);
|
||||||
float rotation = body.Rotation;
|
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransformIgnoreContacts(newPos, rotation));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Vector2 newPos = new Vector2(item.SimPosition.X + dir * doorRectSimSize.X * 1.2f, body.SimPosition.Y);
|
body.SetTransformIgnoreContacts(new Vector2(item.SimPosition.X + dir * doorRectSimSize.X * 1.2f, body.SimPosition.Y), body.Rotation);
|
||||||
float rotation = body.Rotation;
|
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransformIgnoreContacts(newPos, rotation));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using Barotrauma.Networking;
|
|||||||
using FarseerPhysics;
|
using FarseerPhysics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -11,8 +10,11 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
partial class ElectricalDischarger : Powered, IServerSerializable
|
partial class ElectricalDischarger : Powered, IServerSerializable
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<ElectricalDischarger, byte> _dischargerDict = new ConcurrentDictionary<ElectricalDischarger, byte>();
|
private static readonly List<ElectricalDischarger> list = new List<ElectricalDischarger>();
|
||||||
public static IEnumerable<ElectricalDischarger> List => _dischargerDict.Keys;
|
public static IEnumerable<ElectricalDischarger> List
|
||||||
|
{
|
||||||
|
get { return list; }
|
||||||
|
}
|
||||||
|
|
||||||
const int MaxNodes = 100;
|
const int MaxNodes = 100;
|
||||||
const float MaxNodeDistance = 150.0f;
|
const float MaxNodeDistance = 150.0f;
|
||||||
@@ -113,7 +115,7 @@ namespace Barotrauma.Items.Components
|
|||||||
public ElectricalDischarger(Item item, ContentXElement element) :
|
public ElectricalDischarger(Item item, ContentXElement element) :
|
||||||
base(item, element)
|
base(item, element)
|
||||||
{
|
{
|
||||||
_dischargerDict.TryAdd(this, 0);
|
list.Add(this);
|
||||||
|
|
||||||
foreach (var subElement in element.Elements())
|
foreach (var subElement in element.Elements())
|
||||||
{
|
{
|
||||||
@@ -602,7 +604,7 @@ namespace Barotrauma.Items.Components
|
|||||||
protected override void RemoveComponentSpecific()
|
protected override void RemoveComponentSpecific()
|
||||||
{
|
{
|
||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
_dischargerDict.TryRemove(this, out _);
|
list.Remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma.Items.Components
|
namespace Barotrauma.Items.Components
|
||||||
@@ -486,10 +485,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Calculate target position
|
item.body.ResetDynamics();
|
||||||
Vector2 targetPos;
|
|
||||||
Submarine forceSubmarine = picker.Submarine;
|
|
||||||
|
|
||||||
Limb heldHand, arm;
|
Limb heldHand, arm;
|
||||||
if (picker.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand))
|
if (picker.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand))
|
||||||
{
|
{
|
||||||
@@ -507,42 +503,17 @@ namespace Barotrauma.Items.Components
|
|||||||
Vector2 diff = new Vector2(
|
Vector2 diff = new Vector2(
|
||||||
(heldHand.SimPosition.X - arm.SimPosition.X) / 2f,
|
(heldHand.SimPosition.X - arm.SimPosition.X) / 2f,
|
||||||
(heldHand.SimPosition.Y - arm.SimPosition.Y) / 2.5f);
|
(heldHand.SimPosition.Y - arm.SimPosition.Y) / 2.5f);
|
||||||
targetPos = heldHand.SimPosition + diff;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
targetPos = picker.SimPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defer physics operations if in parallel context
|
|
||||||
if (PhysicsBodyQueue.IsInParallelContext)
|
|
||||||
{
|
|
||||||
var capturedBody = item.body;
|
|
||||||
var capturedItem = item;
|
|
||||||
var capturedTargetPos = targetPos;
|
|
||||||
var capturedForceSubmarine = forceSubmarine;
|
|
||||||
|
|
||||||
PhysicsBodyQueue.Enqueue(() =>
|
|
||||||
{
|
|
||||||
if (capturedBody.Removed || capturedItem.Removed) { return; }
|
|
||||||
capturedBody.ResetDynamics();
|
|
||||||
//we have forced the item to be in the same sub as the dropper above,
|
|
||||||
//and are placing it to the position of the hands in "local" coordinates
|
|
||||||
//which may be outside the sub if the character is e.g. standing half-way through the airlock
|
|
||||||
// -> let's use the forceSubmarine argument ensure the item is still considered to be in the sub's coordinate space,
|
|
||||||
// or it will end up in a weird state and seemingly disappear
|
|
||||||
capturedItem.SetTransform(capturedTargetPos, 0.0f, forceSubmarine: capturedForceSubmarine);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item.body.ResetDynamics();
|
|
||||||
//we have forced the item to be in the same sub as the dropper above,
|
//we have forced the item to be in the same sub as the dropper above,
|
||||||
//and are placing it to the position of the hands in "local" coordinates
|
//and are placing it to the position of the hands in "local" coordinates
|
||||||
//which may be outside the sub if the character is e.g. standing half-way through the airlock
|
//which may be outside the sub if the character is e.g. standing half-way through the airlock
|
||||||
// -> let's use the forceSubmarine argument ensure the item is still considered to be in the sub's coordinate space,
|
// -> let's use the forceSubmarine argument ensure the item is still considered to be in the sub's coordinate space,
|
||||||
// or it will end up in a weird state and seemingly disappear
|
// or it will end up in a weird state and seemingly disappear
|
||||||
item.SetTransform(targetPos, 0.0f, forceSubmarine: forceSubmarine);
|
item.SetTransform(heldHand.SimPosition + diff, 0.0f, forceSubmarine: picker.Submarine);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.SetTransform(picker.SimPosition, 0.0f, forceSubmarine: picker.Submarine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,13 +616,12 @@ namespace Barotrauma.Items.Components
|
|||||||
return CanBeAttached(user, out _);
|
return CanBeAttached(user, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly ThreadLocal<List<Item>> tempOverlappingItems = new ThreadLocal<List<Item>>(() => new List<Item>());
|
private static List<Item> tempOverlappingItems = new List<Item>();
|
||||||
|
|
||||||
private bool CanBeAttached(Character user, out IEnumerable<Item> overlappingItems)
|
private bool CanBeAttached(Character user, out IEnumerable<Item> overlappingItems)
|
||||||
{
|
{
|
||||||
var overlapping = tempOverlappingItems.Value;
|
tempOverlappingItems.Clear();
|
||||||
overlapping.Clear();
|
overlappingItems = tempOverlappingItems;
|
||||||
overlappingItems = overlapping;
|
|
||||||
if (!attachable || !Reattachable) { return false; }
|
if (!attachable || !Reattachable) { return false; }
|
||||||
|
|
||||||
//can be attached anywhere in sub editor
|
//can be attached anywhere in sub editor
|
||||||
@@ -694,9 +664,9 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
if (attachPos.X + size.X < worldRect.X || attachPos.X - size.X > worldRect.Right) { continue; }
|
if (attachPos.X + size.X < worldRect.X || attachPos.X - size.X > worldRect.Right) { continue; }
|
||||||
if (attachPos.Y - size.Y > worldRect.Y || attachPos.Y + size.Y < worldRect.Y - worldRect.Height) { continue; }
|
if (attachPos.Y - size.Y > worldRect.Y || attachPos.Y + size.Y < worldRect.Y - worldRect.Height) { continue; }
|
||||||
overlapping.Add(otherItem);
|
tempOverlappingItems.Add(otherItem);
|
||||||
}
|
}
|
||||||
if (overlapping.Any()) { return false; }
|
if (tempOverlappingItems.Any()) { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
//can be attached anywhere inside hulls
|
//can be attached anywhere inside hulls
|
||||||
|
|||||||
@@ -13,12 +13,6 @@ namespace Barotrauma.Items.Components
|
|||||||
private Holdable holdable;
|
private Holdable holdable;
|
||||||
|
|
||||||
private float deattachTimer;
|
private float deattachTimer;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Flag to prevent multiple queued creation requests.
|
|
||||||
/// Uses volatile to ensure visibility across threads.
|
|
||||||
/// </summary>
|
|
||||||
private volatile bool triggerBodyCreationQueued;
|
|
||||||
|
|
||||||
[Serialize(1.0f, IsPropertySaveable.No, description: "How long it takes to deattach the item from the level walls (in seconds).")]
|
[Serialize(1.0f, IsPropertySaveable.No, description: "How long it takes to deattach the item from the level walls (in seconds).")]
|
||||||
public float DeattachDuration
|
public float DeattachDuration
|
||||||
@@ -92,16 +86,13 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (trigger != null && amount.LengthSquared() > 0.00001f)
|
if (trigger != null && amount.LengthSquared() > 0.00001f)
|
||||||
{
|
{
|
||||||
// Defer physics operation if in parallel context (Farseer is not thread-safe)
|
|
||||||
var capturedTrigger = trigger;
|
|
||||||
var capturedPos = item.SimPosition;
|
|
||||||
if (ignoreContacts)
|
if (ignoreContacts)
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => capturedTrigger.SetTransformIgnoreContacts(capturedPos, 0.0f));
|
trigger.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => capturedTrigger.SetTransform(capturedPos, 0.0f));
|
trigger.SetTransform(item.SimPosition, 0.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,29 +109,13 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (trigger == null && !triggerBodyCreationQueued)
|
if (trigger == null)
|
||||||
{
|
{
|
||||||
// Queue the physics body creation to be processed on the main thread.
|
CreateTriggerBody();
|
||||||
// This is necessary because physics body creation is not thread-safe
|
|
||||||
// and Update() may be called from a parallel loop.
|
|
||||||
triggerBodyCreationQueued = true;
|
|
||||||
PhysicsBodyQueue.EnqueueCreation(() =>
|
|
||||||
{
|
|
||||||
// Double-check that trigger hasn't been created yet
|
|
||||||
// (in case this was called multiple times before queue processing)
|
|
||||||
if (trigger == null && !item.Removed)
|
|
||||||
{
|
|
||||||
CreateTriggerBody();
|
|
||||||
}
|
|
||||||
triggerBodyCreationQueued = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (trigger != null && Vector2.DistanceSquared(item.SimPosition, trigger.SimPosition) > 0.01f)
|
if (trigger != null && Vector2.DistanceSquared(item.SimPosition, trigger.SimPosition) > 0.01f)
|
||||||
{
|
{
|
||||||
// Defer physics operation if in parallel context (Farseer is not thread-safe)
|
trigger.SetTransform(item.SimPosition, 0.0f);
|
||||||
var capturedTrigger = trigger;
|
|
||||||
var capturedPos = item.SimPosition;
|
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => capturedTrigger.SetTransform(capturedPos, 0.0f));
|
|
||||||
}
|
}
|
||||||
IsActive = false;
|
IsActive = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using FarseerPhysics.Dynamics;
|
|||||||
using FarseerPhysics.Dynamics.Contacts;
|
using FarseerPhysics.Dynamics.Contacts;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -25,7 +24,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
private readonly HashSet<Entity> hitTargets = new HashSet<Entity>();
|
private readonly HashSet<Entity> hitTargets = new HashSet<Entity>();
|
||||||
|
|
||||||
private readonly ConcurrentQueue<Fixture> impactQueue = new ConcurrentQueue<Fixture>();
|
private readonly Queue<Fixture> impactQueue = new Queue<Fixture>();
|
||||||
|
|
||||||
public Character User { get; private set; }
|
public Character User { get; private set; }
|
||||||
|
|
||||||
@@ -191,16 +190,17 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (!item.body.Enabled)
|
if (!item.body.Enabled)
|
||||||
{
|
{
|
||||||
while (impactQueue.TryDequeue(out _)) { } // Clear queue
|
impactQueue.Clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (picker == null || !picker.HeldItems.Contains(item))
|
if (picker == null || !picker.HeldItems.Contains(item))
|
||||||
{
|
{
|
||||||
while (impactQueue.TryDequeue(out _)) { } // Clear queue
|
impactQueue.Clear();
|
||||||
IsActive = false;
|
IsActive = false;
|
||||||
}
|
}
|
||||||
while (impactQueue.TryDequeue(out var impact))
|
while (impactQueue.Count > 0)
|
||||||
{
|
{
|
||||||
|
var impact = impactQueue.Dequeue();
|
||||||
HandleImpact(impact);
|
HandleImpact(impact);
|
||||||
}
|
}
|
||||||
//in case handling the impact does something to the picker
|
//in case handling the impact does something to the picker
|
||||||
@@ -300,7 +300,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
private void RestoreCollision()
|
private void RestoreCollision()
|
||||||
{
|
{
|
||||||
while (impactQueue.TryDequeue(out _)) { } // Clear queue
|
impactQueue.Clear();
|
||||||
item.body.FarseerBody.OnCollision -= OnCollision;
|
item.body.FarseerBody.OnCollision -= OnCollision;
|
||||||
item.body.CollisionCategories = Physics.CollisionItem;
|
item.body.CollisionCategories = Physics.CollisionItem;
|
||||||
item.body.CollidesWith = Physics.DefaultItemCollidesWith;
|
item.body.CollidesWith = Physics.DefaultItemCollidesWith;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using Microsoft.Xna.Framework;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.MapCreatures.Behavior;
|
using Barotrauma.MapCreatures.Behavior;
|
||||||
|
|
||||||
@@ -316,7 +315,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
partial void UseProjSpecific(float deltaTime, Vector2 raystart);
|
partial void UseProjSpecific(float deltaTime, Vector2 raystart);
|
||||||
|
|
||||||
private static readonly ThreadLocal<List<Body>> hitBodies = new ThreadLocal<List<Body>>(() => new List<Body>());
|
private static readonly List<Body> hitBodies = new List<Body>();
|
||||||
private readonly HashSet<Character> hitCharacters = new HashSet<Character>();
|
private readonly HashSet<Character> hitCharacters = new HashSet<Character>();
|
||||||
private readonly List<FireSource> fireSourcesInRange = new List<FireSource>();
|
private readonly List<FireSource> fireSourcesInRange = new List<FireSource>();
|
||||||
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
|
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
|
||||||
@@ -374,13 +373,13 @@ namespace Barotrauma.Items.Components
|
|||||||
},
|
},
|
||||||
allowInsideFixture: true);
|
allowInsideFixture: true);
|
||||||
|
|
||||||
hitBodies.Value.Clear();
|
hitBodies.Clear();
|
||||||
hitBodies.Value.AddRange(bodies.Distinct());
|
hitBodies.AddRange(bodies.Distinct());
|
||||||
|
|
||||||
lastPickedFraction = Submarine.LastPickedFraction;
|
lastPickedFraction = Submarine.LastPickedFraction;
|
||||||
Type lastHitType = null;
|
Type lastHitType = null;
|
||||||
hitCharacters.Clear();
|
hitCharacters.Clear();
|
||||||
foreach (Body body in hitBodies.Value)
|
foreach (Body body in hitBodies)
|
||||||
{
|
{
|
||||||
Type bodyType = body.UserData?.GetType();
|
Type bodyType = body.UserData?.GetType();
|
||||||
if (!RepairThroughWalls && bodyType != null && bodyType != lastHitType)
|
if (!RepairThroughWalls && bodyType != null && bodyType != lastHitType)
|
||||||
@@ -898,49 +897,48 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly ThreadLocal<List<ISerializableEntity>> currentTargets = new ThreadLocal<List<ISerializableEntity>>(() => new List<ISerializableEntity>());
|
private static List<ISerializableEntity> currentTargets = new List<ISerializableEntity>();
|
||||||
private void ApplyStatusEffectsOnTarget(Character user, float deltaTime, ActionType actionType, Item targetItem = null, Character character = null, Limb limb = null, Structure structure = null)
|
private void ApplyStatusEffectsOnTarget(Character user, float deltaTime, ActionType actionType, Item targetItem = null, Character character = null, Limb limb = null, Structure structure = null)
|
||||||
{
|
{
|
||||||
if (statusEffectLists == null) { return; }
|
if (statusEffectLists == null) { return; }
|
||||||
if (!statusEffectLists.TryGetValue(actionType, out List<StatusEffect> statusEffects)) { return; }
|
if (!statusEffectLists.TryGetValue(actionType, out List<StatusEffect> statusEffects)) { return; }
|
||||||
|
|
||||||
var targets = currentTargets.Value;
|
|
||||||
foreach (StatusEffect effect in statusEffects)
|
foreach (StatusEffect effect in statusEffects)
|
||||||
{
|
{
|
||||||
targets.Clear();
|
currentTargets.Clear();
|
||||||
effect.SetUser(user);
|
effect.SetUser(user);
|
||||||
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
|
||||||
{
|
{
|
||||||
if (targetItem != null)
|
if (targetItem != null)
|
||||||
{
|
{
|
||||||
targets.AddRange(targetItem.AllPropertyObjects);
|
currentTargets.AddRange(targetItem.AllPropertyObjects);
|
||||||
}
|
}
|
||||||
if (structure != null)
|
if (structure != null)
|
||||||
{
|
{
|
||||||
targets.Add(structure);
|
currentTargets.Add(structure);
|
||||||
}
|
}
|
||||||
if (character != null)
|
if (character != null)
|
||||||
{
|
{
|
||||||
targets.Add(character);
|
currentTargets.Add(character);
|
||||||
}
|
}
|
||||||
effect.Apply(actionType, deltaTime, item, targets);
|
effect.Apply(actionType, deltaTime, item, currentTargets);
|
||||||
}
|
}
|
||||||
else if (effect.HasTargetType(StatusEffect.TargetType.Character))
|
else if (effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||||
{
|
{
|
||||||
targets.Add(user);
|
currentTargets.Add(user);
|
||||||
effect.Apply(actionType, deltaTime, item, targets);
|
effect.Apply(actionType, deltaTime, item, currentTargets);
|
||||||
}
|
}
|
||||||
else if (effect.HasTargetType(StatusEffect.TargetType.Limb))
|
else if (effect.HasTargetType(StatusEffect.TargetType.Limb))
|
||||||
{
|
{
|
||||||
targets.Add(limb);
|
currentTargets.Add(limb);
|
||||||
effect.Apply(actionType, deltaTime, item, targets);
|
effect.Apply(actionType, deltaTime, item, currentTargets);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
if (user == null) { return; }
|
if (user == null) { return; }
|
||||||
// Hard-coded progress bars for welding doors stuck.
|
// Hard-coded progress bars for welding doors stuck.
|
||||||
// A general purpose system could be better, but it would most likely require changes in the way we define the status effects in xml.
|
// A general purpose system could be better, but it would most likely require changes in the way we define the status effects in xml.
|
||||||
foreach (ISerializableEntity target in targets)
|
foreach (ISerializableEntity target in currentTargets)
|
||||||
{
|
{
|
||||||
if (target is not Door door) { continue; }
|
if (target is not Door door) { continue; }
|
||||||
if (!door.CanBeWelded || !door.Item.IsInteractable(user)) { continue; }
|
if (!door.CanBeWelded || !door.Item.IsInteractable(user)) { continue; }
|
||||||
|
|||||||
@@ -949,8 +949,7 @@ namespace Barotrauma.Items.Components
|
|||||||
//if any of the effects reduce the item's condition, set the user for OnBroken effects as well
|
//if any of the effects reduce the item's condition, set the user for OnBroken effects as well
|
||||||
if (reducesCondition && user != null && type != ActionType.OnBroken)
|
if (reducesCondition && user != null && type != ActionType.OnBroken)
|
||||||
{
|
{
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (ItemComponent ic in item.Components)
|
||||||
foreach (ItemComponent ic in item.Components.ToArray())
|
|
||||||
{
|
{
|
||||||
if (ic.statusEffectLists == null || !ic.statusEffectLists.TryGetValue(ActionType.OnBroken, out List<StatusEffect> brokenEffects)) { continue; }
|
if (ic.statusEffectLists == null || !ic.statusEffectLists.TryGetValue(ActionType.OnBroken, out List<StatusEffect> brokenEffects)) { continue; }
|
||||||
foreach (var brokenEffect in brokenEffects)
|
foreach (var brokenEffect in brokenEffects)
|
||||||
|
|||||||
@@ -884,8 +884,7 @@ namespace Barotrauma.Items.Components
|
|||||||
RelatedItem containableItem = FindContainableItem(containedItem);
|
RelatedItem containableItem = FindContainableItem(containedItem);
|
||||||
if (containableItem != null && containableItem.SetActive)
|
if (containableItem != null && containableItem.SetActive)
|
||||||
{
|
{
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (var ic in containedItem.Components)
|
||||||
foreach (var ic in containedItem.Components.ToArray())
|
|
||||||
{
|
{
|
||||||
ic.IsActive = active;
|
ic.IsActive = active;
|
||||||
}
|
}
|
||||||
@@ -1011,11 +1010,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (flippedX ^ flippedY) { rotation = -rotation; }
|
if (flippedX ^ flippedY) { rotation = -rotation; }
|
||||||
rotation += -item.RotationRad;
|
rotation += -item.RotationRad;
|
||||||
}
|
}
|
||||||
// Defer physics operation if in parallel context (Farseer is not thread-safe)
|
contained.Item.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, rotation);
|
||||||
var capturedBody = contained.Item.body.FarseerBody;
|
|
||||||
var capturedSimPos = simPos;
|
|
||||||
var capturedRotation = rotation;
|
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransformIgnoreContacts(ref capturedSimPos, capturedRotation));
|
|
||||||
contained.Item.body.UpdateDrawPosition(interpolate: false);
|
contained.Item.body.UpdateDrawPosition(interpolate: false);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma.Items.Components
|
namespace Barotrauma.Items.Components
|
||||||
{
|
{
|
||||||
partial class Ladder : ItemComponent
|
partial class Ladder : ItemComponent
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<Ladder, byte> _ladderDict = new ConcurrentDictionary<Ladder, byte>();
|
public static List<Ladder> List { get; } = new List<Ladder>();
|
||||||
public static IEnumerable<Ladder> List => _ladderDict.Keys;
|
|
||||||
|
|
||||||
public Ladder(Item item, ContentXElement element)
|
public Ladder(Item item, ContentXElement element)
|
||||||
: base(item, element)
|
: base(item, element)
|
||||||
{
|
{
|
||||||
InitProjSpecific(element);
|
InitProjSpecific(element);
|
||||||
_ladderDict.TryAdd(this, 0);
|
List.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void InitProjSpecific(ContentXElement element);
|
partial void InitProjSpecific(ContentXElement element);
|
||||||
@@ -30,7 +28,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
RemoveProjSpecific();
|
RemoveProjSpecific();
|
||||||
_ladderDict.TryRemove(this, out _);
|
List.Remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void RemoveProjSpecific();
|
partial void RemoveProjSpecific();
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Barotrauma.Items.Components
|
namespace Barotrauma.Items.Components
|
||||||
{
|
{
|
||||||
@@ -498,14 +497,12 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: user), positionOut);
|
item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: user), positionOut);
|
||||||
|
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
|
||||||
var signalRecipients = item.LastSentSignalRecipients.ToList();
|
|
||||||
for (int i = signalRecipients.Count - 1; i >= 0; i--)
|
|
||||||
{
|
{
|
||||||
if (signalRecipients[i].Item.Condition <= 0.0f || signalRecipients[i].IsPower) { continue; }
|
if (item.LastSentSignalRecipients[i].Item.Condition <= 0.0f || item.LastSentSignalRecipients[i].IsPower) { continue; }
|
||||||
if (signalRecipients[i].Item.Prefab.FocusOnSelected)
|
if (item.LastSentSignalRecipients[i].Item.Prefab.FocusOnSelected)
|
||||||
{
|
{
|
||||||
return signalRecipients[i].Item;
|
return item.LastSentSignalRecipients[i].Item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Barotrauma.Items.Components
|
namespace Barotrauma.Items.Components
|
||||||
{
|
{
|
||||||
partial class Sonar : Powered, IServerSerializable, IClientSerializable
|
partial class Sonar : Powered, IServerSerializable, IClientSerializable
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<Sonar, byte> _sonarDict = new ConcurrentDictionary<Sonar, byte>();
|
public static List<Sonar> SonarList = new List<Sonar>();
|
||||||
public static IEnumerable<Sonar> SonarList => _sonarDict.Keys;
|
|
||||||
|
|
||||||
public enum Mode
|
public enum Mode
|
||||||
{
|
{
|
||||||
@@ -172,7 +169,7 @@ namespace Barotrauma.Items.Components
|
|||||||
IsActive = true;
|
IsActive = true;
|
||||||
InitProjSpecific(element);
|
InitProjSpecific(element);
|
||||||
CurrentMode = Mode.Passive;
|
CurrentMode = Mode.Passive;
|
||||||
_sonarDict.TryAdd(this, 0);
|
SonarList.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void InitProjSpecific(ContentXElement element);
|
partial void InitProjSpecific(ContentXElement element);
|
||||||
@@ -294,15 +291,13 @@ namespace Barotrauma.Items.Components
|
|||||||
return currentPingIndex != -1 && (character == null || characterUsable);
|
return currentPingIndex != -1 && (character == null || characterUsable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly ThreadLocal<Dictionary<string, List<Character>>> targetGroups =
|
private static readonly Dictionary<string, List<Character>> targetGroups = new Dictionary<string, List<Character>>();
|
||||||
new ThreadLocal<Dictionary<string, List<Character>>>(() => new Dictionary<string, List<Character>>());
|
|
||||||
|
|
||||||
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||||
{
|
{
|
||||||
if (currentMode == Mode.Passive || !aiPingCheckPending) { return false; }
|
if (currentMode == Mode.Passive || !aiPingCheckPending) { return false; }
|
||||||
|
|
||||||
var groups = targetGroups.Value;
|
foreach (List<Character> targetGroup in targetGroups.Values)
|
||||||
foreach (List<Character> targetGroup in groups.Values)
|
|
||||||
{
|
{
|
||||||
targetGroup.Clear();
|
targetGroup.Clear();
|
||||||
}
|
}
|
||||||
@@ -315,14 +310,14 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
#warning This is not the best key for a dictionary.
|
#warning This is not the best key for a dictionary.
|
||||||
string directionName = GetDirectionName(c.WorldPosition - item.WorldPosition).Value;
|
string directionName = GetDirectionName(c.WorldPosition - item.WorldPosition).Value;
|
||||||
if (!groups.ContainsKey(directionName))
|
if (!targetGroups.ContainsKey(directionName))
|
||||||
{
|
{
|
||||||
groups.Add(directionName, new List<Character>());
|
targetGroups.Add(directionName, new List<Character>());
|
||||||
}
|
}
|
||||||
groups[directionName].Add(c);
|
targetGroups[directionName].Add(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (KeyValuePair<string, List<Character>> targetGroup in groups)
|
foreach (KeyValuePair<string, List<Character>> targetGroup in targetGroups)
|
||||||
{
|
{
|
||||||
if (!targetGroup.Value.Any()) { continue; }
|
if (!targetGroup.Value.Any()) { continue; }
|
||||||
string dialogTag = "DialogSonarTarget";
|
string dialogTag = "DialogSonarTarget";
|
||||||
@@ -406,7 +401,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
MineralClusters = null;
|
MineralClusters = null;
|
||||||
#endif
|
#endif
|
||||||
_sonarDict.TryRemove(this, out _);
|
SonarList.Remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -637,7 +637,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (pathFinder == null)
|
if (pathFinder == null)
|
||||||
{
|
{
|
||||||
pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false)
|
pathFinder = new PathFinder(WayPoint.WayPointList, false)
|
||||||
{
|
{
|
||||||
GetNodePenalty = GetNodePenalty
|
GetNodePenalty = GetNodePenalty
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
@@ -14,12 +13,10 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
private readonly HashSet<Connection> signalConnections = new HashSet<Connection>();
|
private readonly HashSet<Connection> signalConnections = new HashSet<Connection>();
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<Connection, bool> connectionDirty = new ConcurrentDictionary<Connection, bool>();
|
private readonly Dictionary<Connection, bool> connectionDirty = new Dictionary<Connection, bool>();
|
||||||
|
|
||||||
//a list of connections a given connection is connected to, either directly or via other power transfer components
|
//a list of connections a given connection is connected to, either directly or via other power transfer components
|
||||||
//Uses ConcurrentDictionary<Connection, byte> as a thread-safe HashSet replacement
|
private readonly Dictionary<Connection, HashSet<Connection>> connectedRecipients = new Dictionary<Connection, HashSet<Connection>>();
|
||||||
private readonly ConcurrentDictionary<Connection, ConcurrentDictionary<Connection, byte>> connectedRecipients =
|
|
||||||
new ConcurrentDictionary<Connection, ConcurrentDictionary<Connection, byte>>();
|
|
||||||
|
|
||||||
private float overloadCooldownTimer;
|
private float overloadCooldownTimer;
|
||||||
private const float OverloadCooldown = 5.0f;
|
private const float OverloadCooldown = 5.0f;
|
||||||
@@ -135,7 +132,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
partial void InitProjectSpecific(XElement element);
|
partial void InitProjectSpecific(XElement element);
|
||||||
|
|
||||||
private static readonly System.Collections.Concurrent.ConcurrentDictionary<PowerTransfer, byte> _recipientsToRefresh = new System.Collections.Concurrent.ConcurrentDictionary<PowerTransfer, byte>();
|
private static readonly HashSet<PowerTransfer> recipientsToRefresh = new HashSet<PowerTransfer>();
|
||||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||||
{
|
{
|
||||||
base.UpdateBroken(deltaTime, cam);
|
base.UpdateBroken(deltaTime, cam);
|
||||||
@@ -147,21 +144,20 @@ namespace Barotrauma.Items.Components
|
|||||||
powerLoad = 0.0f;
|
powerLoad = 0.0f;
|
||||||
currPowerConsumption = 0.0f;
|
currPowerConsumption = 0.0f;
|
||||||
SetAllConnectionsDirty();
|
SetAllConnectionsDirty();
|
||||||
_recipientsToRefresh.Clear();
|
recipientsToRefresh.Clear();
|
||||||
// Take snapshot for thread-safe iteration (no locks needed with ConcurrentDictionary)
|
foreach (HashSet<Connection> recipientList in connectedRecipients.Values)
|
||||||
foreach (var recipientDict in connectedRecipients.Values)
|
|
||||||
{
|
{
|
||||||
foreach (Connection c in recipientDict.Keys)
|
foreach (Connection c in recipientList)
|
||||||
{
|
{
|
||||||
if (c.Item == item) { continue; }
|
if (c.Item == item) { continue; }
|
||||||
var recipientPowerTransfer = c.Item.GetComponent<PowerTransfer>();
|
var recipientPowerTransfer = c.Item.GetComponent<PowerTransfer>();
|
||||||
if (recipientPowerTransfer != null)
|
if (recipientPowerTransfer != null)
|
||||||
{
|
{
|
||||||
_recipientsToRefresh.TryAdd(recipientPowerTransfer, 0);
|
recipientsToRefresh.Add(recipientPowerTransfer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (PowerTransfer recipientPowerTransfer in _recipientsToRefresh.Keys)
|
foreach (PowerTransfer recipientPowerTransfer in recipientsToRefresh)
|
||||||
{
|
{
|
||||||
recipientPowerTransfer.SetAllConnectionsDirty();
|
recipientPowerTransfer.SetAllConnectionsDirty();
|
||||||
recipientPowerTransfer.RefreshConnections();
|
recipientPowerTransfer.RefreshConnections();
|
||||||
@@ -308,56 +304,58 @@ namespace Barotrauma.Items.Components
|
|||||||
protected void RefreshConnections()
|
protected void RefreshConnections()
|
||||||
{
|
{
|
||||||
var connections = item.Connections;
|
var connections = item.Connections;
|
||||||
if (connections == null) { return; }
|
foreach (Connection c in connections)
|
||||||
|
|
||||||
// Take a snapshot of connections for thread-safe iteration
|
|
||||||
var connectionSnapshot = connections.ToList();
|
|
||||||
foreach (Connection c in connectionSnapshot)
|
|
||||||
{
|
{
|
||||||
if (!connectionDirty.TryGetValue(c, out bool isDirty))
|
if (!connectionDirty.ContainsKey(c))
|
||||||
{
|
{
|
||||||
connectionDirty[c] = true;
|
connectionDirty[c] = true;
|
||||||
isDirty = true;
|
|
||||||
}
|
}
|
||||||
|
else if (!connectionDirty[c])
|
||||||
if (!isDirty)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//find all connections that are connected to this one (directly or via another PowerTransfer)
|
//find all connections that are connected to this one (directly or via another PowerTransfer)
|
||||||
var tempConnected = connectedRecipients.GetOrAdd(c, _ => new ConcurrentDictionary<Connection, byte>());
|
HashSet<Connection> tempConnected;
|
||||||
|
if (!connectedRecipients.ContainsKey(c))
|
||||||
// Get previous recipients and clear
|
|
||||||
var previousRecipients = tempConnected.Keys.ToList();
|
|
||||||
tempConnected.Clear();
|
|
||||||
|
|
||||||
//mark all previous recipients as dirty
|
|
||||||
foreach (Connection recipient in previousRecipients)
|
|
||||||
{
|
{
|
||||||
var pt = recipient.Item.GetComponent<PowerTransfer>();
|
tempConnected = new HashSet<Connection>();
|
||||||
if (pt != null) { pt.connectionDirty[recipient] = true; }
|
connectedRecipients.Add(c, tempConnected);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tempConnected = connectedRecipients[c];
|
||||||
|
tempConnected.Clear();
|
||||||
|
//mark all previous recipients as dirty
|
||||||
|
foreach (Connection recipient in tempConnected)
|
||||||
|
{
|
||||||
|
var pt = recipient.Item.GetComponent<PowerTransfer>();
|
||||||
|
if (pt != null) { pt.connectionDirty[recipient] = true; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tempConnected.TryAdd(c, 0);
|
tempConnected.Add(c);
|
||||||
if (item.Condition > 0.0f)
|
if (item.Condition > 0.0f)
|
||||||
{
|
{
|
||||||
GetConnected(c, tempConnected);
|
GetConnected(c, tempConnected);
|
||||||
//go through all the PowerTransfers that we're connected to and set their connections to match the ones we just calculated
|
//go through all the PowerTransfers that we're connected to and set their connections to match the ones we just calculated
|
||||||
//(no need to go through the recursive GetConnected method again)
|
//(no need to go through the recursive GetConnected method again)
|
||||||
// Take snapshot for thread-safe iteration (no locks needed)
|
foreach (Connection recipient in tempConnected)
|
||||||
var tempConnectedSnapshot = tempConnected.Keys.ToList();
|
|
||||||
foreach (Connection recipient in tempConnectedSnapshot)
|
|
||||||
{
|
{
|
||||||
if (recipient == c) { continue; }
|
if (recipient == c) { continue; }
|
||||||
var recipientPowerTransfer = recipient.Item.GetComponent<PowerTransfer>();
|
var recipientPowerTransfer = recipient.Item.GetComponent<PowerTransfer>();
|
||||||
if (recipientPowerTransfer == null) { continue; }
|
if (recipientPowerTransfer == null) { continue; }
|
||||||
|
if (!recipientPowerTransfer.connectedRecipients.ContainsKey(recipient))
|
||||||
var recipientSet = recipientPowerTransfer.connectedRecipients.GetOrAdd(recipient, _ => new ConcurrentDictionary<Connection, byte>());
|
|
||||||
recipientSet.Clear();
|
|
||||||
foreach (var connection in tempConnectedSnapshot)
|
|
||||||
{
|
{
|
||||||
recipientSet.TryAdd(connection, 0);
|
recipientPowerTransfer.connectedRecipients.Add(recipient, new HashSet<Connection>());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recipientPowerTransfer.connectedRecipients[recipient].Clear();
|
||||||
|
}
|
||||||
|
foreach (var connection in tempConnected)
|
||||||
|
{
|
||||||
|
recipientPowerTransfer.connectedRecipients[recipient].Add(connection);
|
||||||
}
|
}
|
||||||
recipientPowerTransfer.connectionDirty[recipient] = false;
|
recipientPowerTransfer.connectionDirty[recipient] = false;
|
||||||
}
|
}
|
||||||
@@ -366,20 +364,19 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Finds all the connections that can receive a signal sent into the given connection and stores them in the concurrent dictionary.
|
//Finds all the connections that can receive a signal sent into the given connection and stores them in the hashset.
|
||||||
private void GetConnected(Connection c, ConcurrentDictionary<Connection, byte> connected)
|
private void GetConnected(Connection c, HashSet<Connection> connected)
|
||||||
{
|
{
|
||||||
// Take snapshot for thread-safe iteration
|
var recipients = c.Recipients;
|
||||||
var recipients = c.Recipients.ToList();
|
|
||||||
|
|
||||||
foreach (Connection recipient in recipients)
|
foreach (Connection recipient in recipients)
|
||||||
{
|
{
|
||||||
if (recipient == null || connected.ContainsKey(recipient)) { continue; }
|
if (recipient == null || connected.Contains(recipient)) { continue; }
|
||||||
|
|
||||||
Item it = recipient.Item;
|
Item it = recipient.Item;
|
||||||
if (it == null || it.Condition <= 0.0f) { continue; }
|
if (it == null || it.Condition <= 0.0f) { continue; }
|
||||||
|
|
||||||
connected.TryAdd(recipient, 0);
|
connected.Add(recipient);
|
||||||
|
|
||||||
var powerTransfer = it.GetComponent<PowerTransfer>();
|
var powerTransfer = it.GetComponent<PowerTransfer>();
|
||||||
if (powerTransfer != null && powerTransfer.CanTransfer && powerTransfer.IsActive)
|
if (powerTransfer != null && powerTransfer.CanTransfer && powerTransfer.IsActive)
|
||||||
@@ -397,14 +394,10 @@ namespace Barotrauma.Items.Components
|
|||||||
connectionDirty[c] = true;
|
connectionDirty[c] = true;
|
||||||
if (c.IsPower)
|
if (c.IsPower)
|
||||||
{
|
{
|
||||||
MarkConnectionChanged(c);
|
ChangedConnections.Add(c);
|
||||||
if (connectedRecipients.TryGetValue(c, out var recipients))
|
if (connectedRecipients.TryGetValue(c, out var recipients))
|
||||||
{
|
{
|
||||||
// No lock needed - ConcurrentDictionary.Keys is thread-safe
|
recipients.Where(c => c.IsPower).ForEach(c => ChangedConnections.Add(c));
|
||||||
foreach (var conn in recipients.Keys.Where(conn => conn.IsPower))
|
|
||||||
{
|
|
||||||
MarkConnectionChanged(conn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,14 +410,10 @@ namespace Barotrauma.Items.Components
|
|||||||
connectionDirty[connection] = true;
|
connectionDirty[connection] = true;
|
||||||
if (connection.IsPower)
|
if (connection.IsPower)
|
||||||
{
|
{
|
||||||
MarkConnectionChanged(connection);
|
ChangedConnections.Add(connection);
|
||||||
if (connectedRecipients.TryGetValue(connection, out var recipients))
|
if (connectedRecipients.TryGetValue(connection, out var recipients))
|
||||||
{
|
{
|
||||||
// No lock needed - ConcurrentDictionary.Keys is thread-safe
|
recipients.Where(c => c.IsPower).ForEach(c => ChangedConnections.Add(c));
|
||||||
foreach (var conn in recipients.Keys.Where(conn => conn.IsPower))
|
|
||||||
{
|
|
||||||
MarkConnectionChanged(conn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -463,19 +452,16 @@ namespace Barotrauma.Items.Components
|
|||||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||||
{
|
{
|
||||||
if (item.Condition <= 0.0f || connection.IsPower) { return; }
|
if (item.Condition <= 0.0f || connection.IsPower) { return; }
|
||||||
if (!connectedRecipients.TryGetValue(connection, out var recipients)) { return; }
|
if (!connectedRecipients.ContainsKey(connection)) { return; }
|
||||||
if (!signalConnections.Contains(connection)) { return; }
|
if (!signalConnections.Contains(connection)) { return; }
|
||||||
|
|
||||||
// No lock needed - ConcurrentDictionary.Keys is thread-safe
|
foreach (Connection recipient in connectedRecipients[connection])
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
|
||||||
foreach (Connection recipient in recipients.Keys.ToList())
|
|
||||||
{
|
{
|
||||||
if (recipient.Item == item || recipient.Item == signal.source) { continue; }
|
if (recipient.Item == item || recipient.Item == signal.source) { continue; }
|
||||||
|
|
||||||
signal.source?.LastSentSignalRecipients.Add(recipient);
|
signal.source?.LastSentSignalRecipients.Add(recipient);
|
||||||
|
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (ItemComponent ic in recipient.Item.Components)
|
||||||
foreach (ItemComponent ic in recipient.Item.Components.ToArray())
|
|
||||||
{
|
{
|
||||||
//other junction boxes don't need to receive the signal in the pass-through signal connections
|
//other junction boxes don't need to receive the signal in the pass-through signal connections
|
||||||
//because we relay it straight to the connected items without going through the whole chain of junction boxes
|
//because we relay it straight to the connected items without going through the whole chain of junction boxes
|
||||||
@@ -485,8 +471,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (recipient.Effects != null && signal.value != "0" && !string.IsNullOrEmpty(signal.value))
|
if (recipient.Effects != null && signal.value != "0" && !string.IsNullOrEmpty(signal.value))
|
||||||
{
|
{
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (StatusEffect effect in recipient.Effects)
|
||||||
foreach (StatusEffect effect in recipient.Effects.ToArray())
|
|
||||||
{
|
{
|
||||||
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
|
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
|
||||||
}
|
}
|
||||||
@@ -499,7 +484,7 @@ namespace Barotrauma.Items.Components
|
|||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
connectedRecipients?.Clear();
|
connectedRecipients?.Clear();
|
||||||
connectionDirty?.Clear();
|
connectionDirty?.Clear();
|
||||||
_recipientsToRefresh.Clear();
|
recipientsToRefresh.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Threading;
|
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -64,77 +62,17 @@ namespace Barotrauma.Items.Components
|
|||||||
protected const float UpdateInterval = (float)Timing.Step;
|
protected const float UpdateInterval = (float)Timing.Step;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of all powered ItemComponents (thread-safe)
|
/// List of all powered ItemComponents
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly ConcurrentDictionary<Powered, byte> _poweredDict = new ConcurrentDictionary<Powered, byte>();
|
private static readonly List<Powered> poweredList = new List<Powered>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cached list for iteration - updated when collection changes
|
|
||||||
/// </summary>
|
|
||||||
private static volatile List<Powered> _cachedPoweredList;
|
|
||||||
private static int _poweredListVersion;
|
|
||||||
|
|
||||||
public static IEnumerable<Powered> PoweredList
|
public static IEnumerable<Powered> PoweredList
|
||||||
{
|
{
|
||||||
get
|
get { return poweredList; }
|
||||||
{
|
|
||||||
var cached = _cachedPoweredList;
|
|
||||||
if (cached != null) return cached;
|
|
||||||
return GetCachedPoweredList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Powered> GetCachedPoweredList()
|
|
||||||
{
|
|
||||||
var newList = _poweredDict.Keys.ToList();
|
|
||||||
_cachedPoweredList = newList;
|
|
||||||
return newList;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void InvalidatePoweredListCache()
|
|
||||||
{
|
|
||||||
_cachedPoweredList = null;
|
|
||||||
Interlocked.Increment(ref _poweredListVersion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public static readonly HashSet<Connection> ChangedConnections = new HashSet<Connection>();
|
||||||
/// Thread-safe set of changed connections
|
|
||||||
/// </summary>
|
|
||||||
private static readonly ConcurrentDictionary<Connection, byte> _changedConnections = new ConcurrentDictionary<Connection, byte>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all changed connections (snapshot)
|
|
||||||
/// </summary>
|
|
||||||
public static ICollection<Connection> ChangedConnections => _changedConnections.Keys;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a connection to the changed set
|
|
||||||
/// </summary>
|
|
||||||
public static void MarkConnectionChanged(Connection c)
|
|
||||||
{
|
|
||||||
_changedConnections.TryAdd(c, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clear all changed connections
|
|
||||||
/// </summary>
|
|
||||||
public static void ClearChangedConnections()
|
|
||||||
{
|
|
||||||
_changedConnections.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove a connection from the changed set
|
|
||||||
/// </summary>
|
|
||||||
public static void UnmarkConnectionChanged(Connection c)
|
|
||||||
{
|
|
||||||
_changedConnections.TryRemove(c, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
public readonly static Dictionary<int, GridInfo> Grids = new Dictionary<int, GridInfo>();
|
||||||
/// Thread-safe grid dictionary
|
|
||||||
/// </summary>
|
|
||||||
public readonly static ConcurrentDictionary<int, GridInfo> Grids = new ConcurrentDictionary<int, GridInfo>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of power currently consumed by the item. Negative values mean that the item is providing power to connected items
|
/// The amount of power currently consumed by the item. Negative values mean that the item is providing power to connected items
|
||||||
@@ -271,8 +209,7 @@ namespace Barotrauma.Items.Components
|
|||||||
public Powered(Item item, ContentXElement element)
|
public Powered(Item item, ContentXElement element)
|
||||||
: base(item, element)
|
: base(item, element)
|
||||||
{
|
{
|
||||||
_poweredDict.TryAdd(this, 0);
|
poweredList.Add(this);
|
||||||
InvalidatePoweredListCache();
|
|
||||||
InitProjectSpecific(element);
|
InitProjectSpecific(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,20 +322,17 @@ namespace Barotrauma.Items.Components
|
|||||||
//don't use cache if there are no existing grids
|
//don't use cache if there are no existing grids
|
||||||
if (Grids.Count > 0 && useCache)
|
if (Grids.Count > 0 && useCache)
|
||||||
{
|
{
|
||||||
// Take a snapshot of changed connections for iteration
|
|
||||||
var changedSnapshot = ChangedConnections.ToList();
|
|
||||||
|
|
||||||
//delete all grids that were affected
|
//delete all grids that were affected
|
||||||
foreach (Connection c in changedSnapshot)
|
foreach (Connection c in ChangedConnections)
|
||||||
{
|
{
|
||||||
if (c.Grid != null)
|
if (c.Grid != null)
|
||||||
{
|
{
|
||||||
Grids.TryRemove(c.Grid.ID, out _);
|
Grids.Remove(c.Grid.ID);
|
||||||
c.Grid = null;
|
c.Grid = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Connection c in changedSnapshot)
|
foreach (Connection c in ChangedConnections)
|
||||||
{
|
{
|
||||||
//Make sure the connection grid hasn't been resolved by another connection update
|
//Make sure the connection grid hasn't been resolved by another connection update
|
||||||
//Ensure the connection has other connections
|
//Ensure the connection has other connections
|
||||||
@@ -412,7 +346,7 @@ namespace Barotrauma.Items.Components
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
//Clear all grid IDs from connections
|
//Clear all grid IDs from connections
|
||||||
foreach (Powered powered in PoweredList)
|
foreach (Powered powered in poweredList)
|
||||||
{
|
{
|
||||||
//Only check devices with connectors
|
//Only check devices with connectors
|
||||||
if (powered.powerIn != null)
|
if (powered.powerIn != null)
|
||||||
@@ -427,7 +361,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
Grids.Clear();
|
Grids.Clear();
|
||||||
|
|
||||||
foreach (Powered powered in PoweredList)
|
foreach (Powered powered in poweredList)
|
||||||
{
|
{
|
||||||
if (powered.Item.Condition <= 0f) { continue; }
|
if (powered.Item.Condition <= 0f) { continue; }
|
||||||
|
|
||||||
@@ -458,7 +392,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Clear changed connections after each update
|
//Clear changed connections after each update
|
||||||
ClearChangedConnections();
|
ChangedConnections.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GridInfo PropagateGrid(Connection conn)
|
private static GridInfo PropagateGrid(Connection conn)
|
||||||
@@ -488,8 +422,8 @@ namespace Barotrauma.Items.Components
|
|||||||
c.Grid = grid;
|
c.Grid = grid;
|
||||||
grid.AddConnection(c);
|
grid.AddConnection(c);
|
||||||
|
|
||||||
//Add on recipients - use ToList() snapshot for thread-safe iteration
|
//Add on recipients
|
||||||
foreach (Connection otherC in c.Recipients.ToList())
|
foreach (Connection otherC in c.Recipients)
|
||||||
{
|
{
|
||||||
//Only add valid connections
|
//Only add valid connections
|
||||||
if (otherC.Grid != grid && (otherC.Grid == null || !Grids.ContainsKey(otherC.Grid.ID)) && ValidPowerConnection(c, otherC))
|
if (otherC.Grid != grid && (otherC.Grid == null || !Grids.ContainsKey(otherC.Grid.ID)) && ValidPowerConnection(c, otherC))
|
||||||
@@ -560,7 +494,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Determine if devices are adding a load or providing power, also resolve solo nodes
|
//Determine if devices are adding a load or providing power, also resolve solo nodes
|
||||||
foreach (Powered powered in PoweredList)
|
foreach (Powered powered in poweredList)
|
||||||
{
|
{
|
||||||
//Make voltage decay to ensure the device powers down.
|
//Make voltage decay to ensure the device powers down.
|
||||||
//This only effects devices with no power input (whose voltage is set by other means, e.g. status effects from a contained battery)
|
//This only effects devices with no power input (whose voltage is set by other means, e.g. status effects from a contained battery)
|
||||||
@@ -796,8 +730,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (item.Connections != null && powerIn != null)
|
if (item.Connections != null && powerIn != null)
|
||||||
{
|
{
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
foreach (Connection recipient in powerIn.Recipients)
|
||||||
foreach (Connection recipient in powerIn.Recipients.ToList())
|
|
||||||
{
|
{
|
||||||
if (!recipient.IsPower || !recipient.IsOutput) { continue; }
|
if (!recipient.IsPower || !recipient.IsOutput) { continue; }
|
||||||
if (recipient.Item?.GetComponent<PowerContainer>() is PowerContainer battery)
|
if (recipient.Item?.GetComponent<PowerContainer>() is PowerContainer battery)
|
||||||
@@ -817,14 +750,13 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (c.IsPower && c.Grid != null)
|
if (c.IsPower && c.Grid != null)
|
||||||
{
|
{
|
||||||
MarkConnectionChanged(c);
|
ChangedConnections.Add(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
_poweredDict.TryRemove(this, out _);
|
poweredList.Remove(this);
|
||||||
InvalidatePoweredListCache();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -848,9 +780,9 @@ namespace Barotrauma.Items.Components
|
|||||||
Connections.Remove(c);
|
Connections.Remove(c);
|
||||||
|
|
||||||
//Remove the grid if it has no devices
|
//Remove the grid if it has no devices
|
||||||
if (Connections.Count == 0)
|
if (Connections.Count == 0 && Powered.Grids.ContainsKey(ID))
|
||||||
{
|
{
|
||||||
Powered.Grids.TryRemove(ID, out _);
|
Powered.Grids.Remove(ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using FarseerPhysics.Dynamics.Contacts;
|
|||||||
using FarseerPhysics.Dynamics.Joints;
|
using FarseerPhysics.Dynamics.Joints;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -73,7 +72,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public const float WaterDragCoefficient = 0.1f;
|
public const float WaterDragCoefficient = 0.1f;
|
||||||
|
|
||||||
private readonly ConcurrentQueue<Impact> impactQueue = new ConcurrentQueue<Impact>();
|
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
|
||||||
|
|
||||||
private bool removePending;
|
private bool removePending;
|
||||||
|
|
||||||
@@ -841,8 +840,9 @@ namespace Barotrauma.Items.Components
|
|||||||
DisableProjectileCollisions();
|
DisableProjectileCollisions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (impactQueue.TryDequeue(out var impact))
|
while (impactQueue.Count > 0)
|
||||||
{
|
{
|
||||||
|
var impact = impactQueue.Dequeue();
|
||||||
HandleProjectileCollision(impact.Fixture, impact.Normal, impact.LinearVelocity);
|
HandleProjectileCollision(impact.Fixture, impact.Normal, impact.LinearVelocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -456,8 +456,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
item.SendSignal(conditionSignal, "condition_out");
|
item.SendSignal(conditionSignal, "condition_out");
|
||||||
|
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (var component in item.Components)
|
||||||
foreach (var component in item.Components.ToArray())
|
|
||||||
{
|
{
|
||||||
if (component is IDeteriorateUnderStress deteriorateUnderStress)
|
if (component is IDeteriorateUnderStress deteriorateUnderStress)
|
||||||
{
|
{
|
||||||
@@ -714,8 +713,7 @@ namespace Barotrauma.Items.Components
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (LastActiveTime > Timing.TotalTime) { return true; }
|
if (LastActiveTime > Timing.TotalTime) { return true; }
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (ItemComponent ic in item.Components)
|
||||||
foreach (ItemComponent ic in item.Components.ToArray())
|
|
||||||
{
|
{
|
||||||
if (ic is Fabricator || ic is Deconstructor)
|
if (ic is Fabricator || ic is Deconstructor)
|
||||||
{
|
{
|
||||||
@@ -763,8 +761,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
private float GetDeteriorationDelayMultiplier()
|
private float GetDeteriorationDelayMultiplier()
|
||||||
{
|
{
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (ItemComponent ic in item.Components)
|
||||||
foreach (ItemComponent ic in item.Components.ToArray())
|
|
||||||
{
|
{
|
||||||
if (ic is Engine engine)
|
if (ic is Engine engine)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -222,14 +222,13 @@ namespace Barotrauma.Items.Components
|
|||||||
public void SetRecipientsDirty()
|
public void SetRecipientsDirty()
|
||||||
{
|
{
|
||||||
recipientsDirty = true;
|
recipientsDirty = true;
|
||||||
if (IsPower) { Powered.MarkConnectionChanged(this); }
|
if (IsPower) { Powered.ChangedConnections.Add(this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshRecipients()
|
private void RefreshRecipients()
|
||||||
{
|
{
|
||||||
recipients.Clear();
|
recipients.Clear();
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (var wire in wires)
|
||||||
foreach (var wire in wires.ToArray())
|
|
||||||
{
|
{
|
||||||
Connection recipient = wire.OtherConnection(this);
|
Connection recipient = wire.OtherConnection(this);
|
||||||
if (recipient != null) { recipients.Add(recipient); }
|
if (recipient != null) { recipients.Add(recipient); }
|
||||||
@@ -268,8 +267,8 @@ namespace Barotrauma.Items.Components
|
|||||||
//Check if both connections belong to a larger grid
|
//Check if both connections belong to a larger grid
|
||||||
if (prevOtherConnection.recipients.Count > 1 && recipients.Count > 1)
|
if (prevOtherConnection.recipients.Count > 1 && recipients.Count > 1)
|
||||||
{
|
{
|
||||||
Powered.MarkConnectionChanged(prevOtherConnection);
|
Powered.ChangedConnections.Add(prevOtherConnection);
|
||||||
Powered.MarkConnectionChanged(this);
|
Powered.ChangedConnections.Add(this);
|
||||||
}
|
}
|
||||||
else if (recipients.Count > 1)
|
else if (recipients.Count > 1)
|
||||||
{
|
{
|
||||||
@@ -285,7 +284,7 @@ namespace Barotrauma.Items.Components
|
|||||||
else if (Grid.Connections.Count == 2)
|
else if (Grid.Connections.Count == 2)
|
||||||
{
|
{
|
||||||
//Delete the grid as these were the only 2 devices
|
//Delete the grid as these were the only 2 devices
|
||||||
Powered.Grids.TryRemove(Grid.ID, out _);
|
Powered.Grids.Remove(Grid.ID);
|
||||||
Grid = null;
|
Grid = null;
|
||||||
prevOtherConnection.Grid = null;
|
prevOtherConnection.Grid = null;
|
||||||
}
|
}
|
||||||
@@ -326,8 +325,8 @@ namespace Barotrauma.Items.Components
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
//Flag change so that proper grids can be formed
|
//Flag change so that proper grids can be formed
|
||||||
Powered.MarkConnectionChanged(this);
|
Powered.ChangedConnections.Add(this);
|
||||||
Powered.MarkConnectionChanged(otherConnection);
|
Powered.ChangedConnections.Add(otherConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,8 +339,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
LastSentSignal = signal;
|
LastSentSignal = signal;
|
||||||
enumeratingWires = true;
|
enumeratingWires = true;
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (var wire in wires)
|
||||||
foreach (var wire in wires.ToArray())
|
|
||||||
{
|
{
|
||||||
Connection recipient = wire.OtherConnection(this);
|
Connection recipient = wire.OtherConnection(this);
|
||||||
if (recipient == null) { continue; }
|
if (recipient == null) { continue; }
|
||||||
@@ -356,14 +354,14 @@ namespace Barotrauma.Items.Components
|
|||||||
GameMain.LuaCs.Hook.Call("signalReceived." + recipient.item.Prefab.Identifier, signal, recipient);
|
GameMain.LuaCs.Hook.Call("signalReceived." + recipient.item.Prefab.Identifier, signal, recipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (CircuitBoxConnection connection in CircuitBoxConnections.ToArray())
|
foreach (CircuitBoxConnection connection in CircuitBoxConnections)
|
||||||
{
|
{
|
||||||
connection.ReceiveSignal(signal);
|
connection.ReceiveSignal(signal);
|
||||||
GameMain.LuaCs.Hook.Call("signalReceived", signal, connection.Connection);
|
GameMain.LuaCs.Hook.Call("signalReceived", signal, connection.Connection);
|
||||||
GameMain.LuaCs.Hook.Call("signalReceived." + connection.Connection.Item.Prefab.Identifier, signal, connection);
|
GameMain.LuaCs.Hook.Call("signalReceived." + connection.Connection.Item.Prefab.Identifier, signal, connection);
|
||||||
}
|
}
|
||||||
enumeratingWires = false;
|
enumeratingWires = false;
|
||||||
foreach (var removedWire in removedWires.ToArray())
|
foreach (var removedWire in removedWires)
|
||||||
{
|
{
|
||||||
wires.Remove(removedWire);
|
wires.Remove(removedWire);
|
||||||
}
|
}
|
||||||
@@ -374,16 +372,14 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
conn.LastReceivedSignal = signal;
|
conn.LastReceivedSignal = signal;
|
||||||
|
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (ItemComponent ic in conn.item.Components)
|
||||||
foreach (ItemComponent ic in conn.item.Components.ToArray())
|
|
||||||
{
|
{
|
||||||
ic.ReceiveSignal(signal, conn);
|
ic.ReceiveSignal(signal, conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conn.Effects == null || signal.value == "0") { return; }
|
if (conn.Effects == null || signal.value == "0") { return; }
|
||||||
|
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (StatusEffect effect in conn.Effects)
|
||||||
foreach (StatusEffect effect in conn.Effects.ToArray())
|
|
||||||
{
|
{
|
||||||
conn.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step);
|
conn.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step);
|
||||||
}
|
}
|
||||||
@@ -393,15 +389,13 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (IsPower && Grid != null)
|
if (IsPower && Grid != null)
|
||||||
{
|
{
|
||||||
Powered.MarkConnectionChanged(this);
|
Powered.ChangedConnections.Add(this);
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (Connection c in recipients)
|
||||||
foreach (Connection c in recipients.ToArray())
|
|
||||||
{
|
{
|
||||||
Powered.MarkConnectionChanged(c);
|
Powered.ChangedConnections.Add(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (var wire in wires)
|
||||||
foreach (var wire in wires.ToArray())
|
|
||||||
{
|
{
|
||||||
wire.RemoveConnection(this);
|
wire.RemoveConnection(this);
|
||||||
recipientsDirty = true;
|
recipientsDirty = true;
|
||||||
@@ -409,7 +403,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (enumeratingWires)
|
if (enumeratingWires)
|
||||||
{
|
{
|
||||||
foreach (var wire in wires.ToArray())
|
foreach (var wire in wires)
|
||||||
{
|
{
|
||||||
removedWires.Add(wire);
|
removedWires.Add(wire);
|
||||||
}
|
}
|
||||||
@@ -452,8 +446,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
XElement newElement = new XElement(IsOutput ? "output" : "input", new XAttribute("name", Name));
|
XElement newElement = new XElement(IsOutput ? "output" : "input", new XAttribute("name", Name));
|
||||||
|
|
||||||
// Use ToArray() snapshot before OrderBy for thread-safe iteration
|
foreach (var wire in wires.OrderBy(w => w.Item.ID))
|
||||||
foreach (var wire in wires.ToArray().OrderBy(w => w.Item.ID))
|
|
||||||
{
|
{
|
||||||
newElement.Add(new XElement("link",
|
newElement.Add(new XElement("link",
|
||||||
new XAttribute("w", wire.Item.ID.ToString()),
|
new XAttribute("w", wire.Item.ID.ToString()),
|
||||||
|
|||||||
@@ -148,16 +148,14 @@ namespace Barotrauma.Items.Components
|
|||||||
Vector2 wireNodeOffset = item.Submarine == null ? Vector2.Zero : item.Submarine.HiddenSubPosition + amount;
|
Vector2 wireNodeOffset = item.Submarine == null ? Vector2.Zero : item.Submarine.HiddenSubPosition + amount;
|
||||||
foreach (Connection c in Connections)
|
foreach (Connection c in Connections)
|
||||||
{
|
{
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (Wire wire in c.Wires)
|
||||||
foreach (Wire wire in c.Wires.ToArray())
|
|
||||||
{
|
{
|
||||||
if (wire == null) { continue; }
|
if (wire == null) { continue; }
|
||||||
TryMoveWire(wire);
|
TryMoveWire(wire);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
foreach (var wire in DisconnectedWires)
|
||||||
foreach (var wire in DisconnectedWires.ToList())
|
|
||||||
{
|
{
|
||||||
TryMoveWire(wire);
|
TryMoveWire(wire);
|
||||||
}
|
}
|
||||||
@@ -389,7 +387,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
foreach (var connection in Connections)
|
foreach (var connection in Connections)
|
||||||
{
|
{
|
||||||
Powered.UnmarkConnectionChanged(connection);
|
Powered.ChangedConnections.Remove(connection);
|
||||||
connection.Recipients.Clear();
|
connection.Recipients.Clear();
|
||||||
}
|
}
|
||||||
Connections.Clear();
|
Connections.Clear();
|
||||||
@@ -414,19 +412,15 @@ namespace Barotrauma.Items.Components
|
|||||||
msg.WriteByte((byte)Connections.Count);
|
msg.WriteByte((byte)Connections.Count);
|
||||||
foreach (Connection connection in Connections)
|
foreach (Connection connection in Connections)
|
||||||
{
|
{
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
msg.WriteVariableUInt32((uint)connection.Wires.Count);
|
||||||
var wiresSnapshot = connection.Wires.ToArray();
|
foreach (Wire wire in connection.Wires)
|
||||||
msg.WriteVariableUInt32((uint)wiresSnapshot.Length);
|
|
||||||
foreach (Wire wire in wiresSnapshot)
|
|
||||||
{
|
{
|
||||||
msg.WriteUInt16(wire?.Item == null ? (ushort)0 : wire.Item.ID);
|
msg.WriteUInt16(wire?.Item == null ? (ushort)0 : wire.Item.ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
msg.WriteUInt16((ushort)DisconnectedWires.Count);
|
||||||
var disconnectedSnapshot = DisconnectedWires.ToList();
|
foreach (Wire disconnectedWire in DisconnectedWires)
|
||||||
msg.WriteUInt16((ushort)disconnectedSnapshot.Count);
|
|
||||||
foreach (Wire disconnectedWire in disconnectedSnapshot)
|
|
||||||
{
|
{
|
||||||
msg.WriteUInt16(disconnectedWire.Item.ID);
|
msg.WriteUInt16(disconnectedWire.Item.ID);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
namespace Barotrauma.Items.Components
|
namespace Barotrauma.Items.Components
|
||||||
@@ -27,8 +25,7 @@ namespace Barotrauma.Items.Components
|
|||||||
private int signalQueueSize;
|
private int signalQueueSize;
|
||||||
private int delayTicks;
|
private int delayTicks;
|
||||||
|
|
||||||
// Thread-safe queue for concurrent access
|
private readonly Queue<DelayedSignal> signalQueue = new Queue<DelayedSignal>();
|
||||||
private readonly ConcurrentQueue<DelayedSignal> signalQueue = new ConcurrentQueue<DelayedSignal>();
|
|
||||||
|
|
||||||
private DelayedSignal prevQueuedSignal;
|
private DelayedSignal prevQueuedSignal;
|
||||||
|
|
||||||
@@ -43,8 +40,7 @@ namespace Barotrauma.Items.Components
|
|||||||
delay = value;
|
delay = value;
|
||||||
delayTicks = (int)(delay / Timing.Step);
|
delayTicks = (int)(delay / Timing.Step);
|
||||||
signalQueueSize = Math.Max(delayTicks, 1) * 2;
|
signalQueueSize = Math.Max(delayTicks, 1) * 2;
|
||||||
// ConcurrentQueue doesn't have Clear(), drain it instead
|
signalQueue.Clear();
|
||||||
while (signalQueue.TryDequeue(out _)) { }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,19 +66,19 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public override void Update(float deltaTime, Camera cam)
|
public override void Update(float deltaTime, Camera cam)
|
||||||
{
|
{
|
||||||
if (signalQueue.IsEmpty)
|
if (signalQueue.Count == 0)
|
||||||
{
|
{
|
||||||
IsActive = false;
|
IsActive = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (var val in signalQueue)
|
||||||
foreach (var val in signalQueue.ToArray())
|
|
||||||
{
|
{
|
||||||
val.SendTimer -= 1;
|
val.SendTimer -= 1;
|
||||||
}
|
}
|
||||||
while (signalQueue.TryPeek(out var signalOut) && signalOut.SendTimer <= 0)
|
while (signalQueue.Count > 0 && signalQueue.Peek().SendTimer <= 0)
|
||||||
{
|
{
|
||||||
|
var signalOut = signalQueue.Peek();
|
||||||
signalOut.SendDuration -= 1;
|
signalOut.SendDuration -= 1;
|
||||||
item.SendSignal(new Signal(signalOut.Signal.value, sender: signalOut.Signal.sender, strength: signalOut.Signal.strength), "signal_out");
|
item.SendSignal(new Signal(signalOut.Signal.value, sender: signalOut.Signal.sender, strength: signalOut.Signal.strength), "signal_out");
|
||||||
if (signalOut.SendDuration <= 0)
|
if (signalOut.SendDuration <= 0)
|
||||||
@@ -104,15 +100,11 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
case "signal_in":
|
case "signal_in":
|
||||||
if (signalQueue.Count >= signalQueueSize) { return; }
|
if (signalQueue.Count >= signalQueueSize) { return; }
|
||||||
if (ResetWhenSignalReceived)
|
if (ResetWhenSignalReceived) { prevQueuedSignal = null; signalQueue.Clear(); }
|
||||||
{
|
if (ResetWhenDifferentSignalReceived && signalQueue.Count > 0 && signalQueue.Peek().Signal.value != signal.value)
|
||||||
prevQueuedSignal = null;
|
|
||||||
while (signalQueue.TryDequeue(out _)) { }
|
|
||||||
}
|
|
||||||
if (ResetWhenDifferentSignalReceived && signalQueue.TryPeek(out var peekSignal) && peekSignal.Signal.value != signal.value)
|
|
||||||
{
|
{
|
||||||
prevQueuedSignal = null;
|
prevQueuedSignal = null;
|
||||||
while (signalQueue.TryDequeue(out _)) { }
|
signalQueue.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevQueuedSignal != null &&
|
if (prevQueuedSignal != null &&
|
||||||
@@ -135,10 +127,10 @@ namespace Barotrauma.Items.Components
|
|||||||
if (float.TryParse(signal.value, NumberStyles.Any, CultureInfo.InvariantCulture, out float newDelay))
|
if (float.TryParse(signal.value, NumberStyles.Any, CultureInfo.InvariantCulture, out float newDelay))
|
||||||
{
|
{
|
||||||
newDelay = MathHelper.Clamp(newDelay, 0, 60);
|
newDelay = MathHelper.Clamp(newDelay, 0, 60);
|
||||||
if (!signalQueue.IsEmpty && newDelay != Delay)
|
if (signalQueue.Count > 0 && newDelay != Delay)
|
||||||
{
|
{
|
||||||
prevQueuedSignal = null;
|
prevQueuedSignal = null;
|
||||||
while (signalQueue.TryDequeue(out _)) { }
|
signalQueue.Clear();
|
||||||
}
|
}
|
||||||
Delay = newDelay;
|
Delay = newDelay;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
@@ -32,8 +31,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
private float thirdInverseMax = 0, loadEqnConstant = 0;
|
private float thirdInverseMax = 0, loadEqnConstant = 0;
|
||||||
|
|
||||||
// Thread-safe immutable dictionary for connection pairs (read-only after initialization)
|
private static readonly Dictionary<string, string> connectionPairs = new Dictionary<string, string>
|
||||||
private static readonly ImmutableDictionary<string, string> connectionPairs = new Dictionary<string, string>
|
|
||||||
{
|
{
|
||||||
{ "power_in", "power_out"},
|
{ "power_in", "power_out"},
|
||||||
{ "signal_in", "signal_out" },
|
{ "signal_in", "signal_out" },
|
||||||
@@ -42,7 +40,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{ "signal_in3", "signal_out3" },
|
{ "signal_in3", "signal_out3" },
|
||||||
{ "signal_in4", "signal_out4" },
|
{ "signal_in4", "signal_out4" },
|
||||||
{ "signal_in5", "signal_out5" }
|
{ "signal_in5", "signal_out5" }
|
||||||
}.ToImmutableDictionary();
|
};
|
||||||
|
|
||||||
protected override PowerPriority Priority { get { return PowerPriority.Relay; } }
|
protected override PowerPriority Priority { get { return PowerPriority.Relay; } }
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -11,8 +10,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
partial class WifiComponent : ItemComponent, IServerSerializable, IClientSerializable
|
partial class WifiComponent : ItemComponent, IServerSerializable, IClientSerializable
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<WifiComponent, byte> _wifiDict = new ConcurrentDictionary<WifiComponent, byte>();
|
private static readonly List<WifiComponent> list = new List<WifiComponent>();
|
||||||
private static IEnumerable<WifiComponent> AllWifiComponents => _wifiDict.Keys;
|
|
||||||
|
|
||||||
const int ChannelMemorySize = 10;
|
const int ChannelMemorySize = 10;
|
||||||
|
|
||||||
@@ -113,7 +111,7 @@ namespace Barotrauma.Items.Components
|
|||||||
public WifiComponent(Item item, ContentXElement element)
|
public WifiComponent(Item item, ContentXElement element)
|
||||||
: base (item, element)
|
: base (item, element)
|
||||||
{
|
{
|
||||||
_wifiDict.TryAdd(this, 0);
|
list.Add(this);
|
||||||
IsActive = true;
|
IsActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +156,7 @@ namespace Barotrauma.Items.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<WifiComponent> GetReceiversInRange()
|
public IEnumerable<WifiComponent> GetReceiversInRange()
|
||||||
{
|
{
|
||||||
return AllWifiComponents.Where(w => w != this && w.CanReceive(this));
|
return list.Where(w => w != this && w.CanReceive(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanReceive(WifiComponent sender)
|
public bool CanReceive(WifiComponent sender)
|
||||||
@@ -187,7 +185,7 @@ namespace Barotrauma.Items.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<WifiComponent> GetTransmittersInRange()
|
public IEnumerable<WifiComponent> GetTransmittersInRange()
|
||||||
{
|
{
|
||||||
return AllWifiComponents.Where(w => w != this && w.CanTransmit(this));
|
return list.Where(w => w != this && w.CanTransmit(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanTransmit(WifiComponent sender)
|
public bool CanTransmit(WifiComponent sender)
|
||||||
@@ -277,8 +275,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (signal.source != null)
|
if (signal.source != null)
|
||||||
{
|
{
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
foreach (Connection receiver in wifiComp.item.LastSentSignalRecipients)
|
||||||
foreach (Connection receiver in wifiComp.item.LastSentSignalRecipients.ToList())
|
|
||||||
{
|
{
|
||||||
if (!signal.source.LastSentSignalRecipients.Contains(receiver))
|
if (!signal.source.LastSentSignalRecipients.Contains(receiver))
|
||||||
{
|
{
|
||||||
@@ -369,7 +366,7 @@ namespace Barotrauma.Items.Components
|
|||||||
protected override void RemoveComponentSpecific()
|
protected override void RemoveComponentSpecific()
|
||||||
{
|
{
|
||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
_wifiDict.TryRemove(this, out _);
|
list.Remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override XElement Save(XElement parentElement)
|
public override XElement Save(XElement parentElement)
|
||||||
|
|||||||
@@ -250,8 +250,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (connections[0] != null && connections[1] != null)
|
if (connections[0] != null && connections[1] != null)
|
||||||
{
|
{
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (ItemComponent ic in item.Components)
|
||||||
foreach (ItemComponent ic in item.Components.ToArray())
|
|
||||||
{
|
{
|
||||||
if (ic == this) { continue; }
|
if (ic == this) { continue; }
|
||||||
|
|
||||||
@@ -724,11 +723,11 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (item0 == null && item1 != null)
|
if (item0 == null && item1 != null)
|
||||||
{
|
{
|
||||||
item0 = Item.ItemList.FirstOrDefault(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(this) ?? false);
|
item0 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(this) ?? false);
|
||||||
}
|
}
|
||||||
else if (item0 != null && item1 == null)
|
else if (item0 != null && item1 == null)
|
||||||
{
|
{
|
||||||
item1 = Item.ItemList.FirstOrDefault(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(this) ?? false);
|
item1 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(this) ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item0 == null || item1 == null || nodes.Count == 0) { return; }
|
if (item0 == null || item1 == null || nodes.Count == 0) { return; }
|
||||||
|
|||||||
@@ -73,11 +73,6 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public PhysicsBody PhysicsBody { get; private set; }
|
public PhysicsBody PhysicsBody { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Flag to prevent multiple queued refresh requests.
|
|
||||||
/// </summary>
|
|
||||||
private volatile bool physicsBodyRefreshQueued;
|
|
||||||
|
|
||||||
private float radius;
|
private float radius;
|
||||||
[Editable, Serialize(0.0f, IsPropertySaveable.Yes)]
|
[Editable, Serialize(0.0f, IsPropertySaveable.Yes)]
|
||||||
public float Radius
|
public float Radius
|
||||||
@@ -88,7 +83,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (radius == value) { return; }
|
if (radius == value) { return; }
|
||||||
radius = value;
|
radius = value;
|
||||||
if (PhysicsBody != null) { QueuePhysicsBodyRefresh(); }
|
if (PhysicsBody != null) { RefreshPhysicsBodySize(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +97,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (width == value) { return; }
|
if (width == value) { return; }
|
||||||
width = value;
|
width = value;
|
||||||
if (PhysicsBody != null) { QueuePhysicsBodyRefresh(); }
|
if (PhysicsBody != null) { RefreshPhysicsBodySize(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,28 +111,10 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (height == value) { return; }
|
if (height == value) { return; }
|
||||||
height = value;
|
height = value;
|
||||||
if (PhysicsBody != null) { QueuePhysicsBodyRefresh(); }
|
if (PhysicsBody != null) { RefreshPhysicsBodySize(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Queue the physics body refresh to be executed on the main thread.
|
|
||||||
/// This is necessary because physics body operations are not thread-safe.
|
|
||||||
/// </summary>
|
|
||||||
private void QueuePhysicsBodyRefresh()
|
|
||||||
{
|
|
||||||
if (physicsBodyRefreshQueued) { return; }
|
|
||||||
physicsBodyRefreshQueued = true;
|
|
||||||
PhysicsBodyQueue.EnqueueCreation(() =>
|
|
||||||
{
|
|
||||||
if (!item.Removed)
|
|
||||||
{
|
|
||||||
RefreshPhysicsBodySize();
|
|
||||||
}
|
|
||||||
physicsBodyRefreshQueued = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private float currentRadius, currentWidth, currentHeight;
|
private float currentRadius, currentWidth, currentHeight;
|
||||||
|
|
||||||
private Vector2 bodyOffset;
|
private Vector2 bodyOffset;
|
||||||
@@ -312,18 +289,13 @@ namespace Barotrauma.Items.Components
|
|||||||
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
|
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
|
||||||
offset = Vector2.Transform(offset, transform);
|
offset = Vector2.Transform(offset, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer physics operations if in parallel context (Farseer is not thread-safe)
|
|
||||||
var capturedBody = PhysicsBody;
|
|
||||||
var capturedPos = item.SimPosition + offset;
|
|
||||||
var capturedRot = -item.RotationRad;
|
|
||||||
if (ignoreContacts)
|
if (ignoreContacts)
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransformIgnoreContacts(capturedPos, capturedRot));
|
PhysicsBody.SetTransformIgnoreContacts(item.SimPosition + offset, -item.RotationRad);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransform(capturedPos, capturedRot));
|
PhysicsBody.SetTransform(item.SimPosition + offset, -item.RotationRad);
|
||||||
}
|
}
|
||||||
PhysicsBody.UpdateDrawPosition();
|
PhysicsBody.UpdateDrawPosition();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -381,8 +381,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (Owner is not Item it) { return; }
|
if (Owner is not Item it) { return; }
|
||||||
|
|
||||||
// Use ToArray() snapshot for thread-safe iteration
|
foreach (var c in it.Components)
|
||||||
foreach (var c in it.Components.ToArray())
|
|
||||||
{
|
{
|
||||||
c.OnInventoryChanged();
|
c.OnInventoryChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ using Barotrauma.Extensions;
|
|||||||
using Barotrauma.MapCreatures.Behavior;
|
using Barotrauma.MapCreatures.Behavior;
|
||||||
using MoonSharp.Interpreter;
|
using MoonSharp.Interpreter;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Threading;
|
|
||||||
using Barotrauma.Abilities;
|
using Barotrauma.Abilities;
|
||||||
using HarmonyLib;
|
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
@@ -29,172 +27,57 @@ namespace Barotrauma
|
|||||||
#region Lists
|
#region Lists
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Thread-safe dictionary of all items by ID.
|
/// A list of every item that exists somewhere in the world. Note that there can be a huge number of items in the list,
|
||||||
|
/// and you probably shouldn't be enumerating it to find some that match some specific criteria (unless that's done very, very sparsely or during initialization).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly ConcurrentDictionary<ushort, Item> _itemDictionary = new ConcurrentDictionary<ushort, Item>();
|
public static readonly List<Item> ItemList = new List<Item>();
|
||||||
|
|
||||||
/// <summary>
|
private static readonly HashSet<Item> _dangerousItems = new HashSet<Item>();
|
||||||
/// Provides thread-safe enumeration over all items.
|
|
||||||
/// </summary>
|
|
||||||
public static ICollection<Item> ItemList => _itemDictionary.Values;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe item lookup by ID.
|
|
||||||
/// </summary>
|
|
||||||
public static Item GetItemById(ushort id)
|
|
||||||
{
|
|
||||||
_itemDictionary.TryGetValue(id, out var item);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thread-safe optimized item collections using Immutable + atomic swap pattern
|
|
||||||
private static volatile ImmutableHashSet<Item> _dangerousItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
private static volatile ImmutableHashSet<Item> _repairableItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
private static volatile ImmutableHashSet<Item> _cleanableItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
private static volatile ImmutableHashSet<Item> _sonarVisibleItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
private static volatile ImmutableHashSet<Item> _turretTargetItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
private static volatile ImmutableHashSet<Item> _chairItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
|
|
||||||
// DeconstructItems uses ConcurrentDictionary to simulate a thread-safe HashSet
|
|
||||||
private static readonly ConcurrentDictionary<Item, byte> _deconstructItems = new ConcurrentDictionary<Item, byte>();
|
|
||||||
|
|
||||||
public static IReadOnlyCollection<Item> DangerousItems => _dangerousItems;
|
public static IReadOnlyCollection<Item> DangerousItems => _dangerousItems;
|
||||||
|
|
||||||
|
private static readonly List<Item> _repairableItems = new List<Item>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Items that have one more more Repairable component
|
/// Items that have one more more Repairable component
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IReadOnlyCollection<Item> RepairableItems => _repairableItems;
|
public static IReadOnlyCollection<Item> RepairableItems => _repairableItems;
|
||||||
|
|
||||||
|
private static readonly List<Item> _cleanableItems = new List<Item>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Items that may potentially need to be cleaned up (pickable, not attached to a wall, and not inside a valid container)
|
/// Items that may potentially need to be cleaned up (pickable, not attached to a wall, and not inside a valid container)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IReadOnlyCollection<Item> CleanableItems => _cleanableItems;
|
public static IReadOnlyCollection<Item> CleanableItems => _cleanableItems;
|
||||||
|
|
||||||
|
private static readonly HashSet<Item> _deconstructItems = new HashSet<Item>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Items that have been marked for deconstruction. Thread-safe collection.
|
/// Items that have been marked for deconstruction
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ICollection<Item> DeconstructItems => _deconstructItems.Keys;
|
public static HashSet<Item> DeconstructItems => _deconstructItems;
|
||||||
|
|
||||||
|
private static readonly List<Item> _sonarVisibleItems = new List<Item>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Items whose <see cref="ItemPrefab.SonarSize"/> is larger than 0
|
/// Items whose <see cref="ItemPrefab.SonarSize"/> is larger than 0
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IReadOnlyCollection<Item> SonarVisibleItems => _sonarVisibleItems;
|
public static IReadOnlyCollection<Item> SonarVisibleItems => _sonarVisibleItems;
|
||||||
|
|
||||||
|
private static readonly List<Item> _turretTargetItems = new List<Item>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Items whose <see cref="ItemPrefab.IsAITurretTarget"/> is true.
|
/// Items whose <see cref="ItemPrefab.IsAITurretTarget"/> is true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IReadOnlyCollection<Item> TurretTargetItems => _turretTargetItems;
|
public static IReadOnlyCollection<Item> TurretTargetItems => _turretTargetItems;
|
||||||
|
|
||||||
|
private static readonly List<Item> _chairItems = new List<Item>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Items that have the tag <see cref="Tags.ChairItem"/>. Which is an oddly specific thing, but useful as an optimization for NPC AI.
|
/// Items that have the tag <see cref="Tags.ChairItem"/>. Which is an oddly specific thing, but useful as an optimization for NPC AI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IReadOnlyCollection<Item> ChairItems => _chairItems;
|
public static IReadOnlyCollection<Item> ChairItems => _chairItems;
|
||||||
|
|
||||||
#region Thread-safe collection helpers
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Atomically adds an item to an immutable set using compare-and-swap.
|
|
||||||
/// </summary>
|
|
||||||
private static void AddToImmutableSet(ref ImmutableHashSet<Item> location, Item item)
|
|
||||||
{
|
|
||||||
ImmutableHashSet<Item> original, updated;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
original = location;
|
|
||||||
updated = original.Add(item);
|
|
||||||
if (ReferenceEquals(original, updated)) return; // Already exists
|
|
||||||
}
|
|
||||||
while (Interlocked.CompareExchange(ref location, updated, original) != original);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Atomically removes an item from an immutable set using compare-and-swap.
|
|
||||||
/// </summary>
|
|
||||||
private static void RemoveFromImmutableSet(ref ImmutableHashSet<Item> location, Item item)
|
|
||||||
{
|
|
||||||
ImmutableHashSet<Item> original, updated;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
original = location;
|
|
||||||
updated = original.Remove(item);
|
|
||||||
if (ReferenceEquals(original, updated)) return; // Doesn't exist
|
|
||||||
}
|
|
||||||
while (Interlocked.CompareExchange(ref location, updated, original) != original);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Marks an item for deconstruction (thread-safe).
|
|
||||||
/// </summary>
|
|
||||||
public static void MarkForDeconstruction(Item item)
|
|
||||||
{
|
|
||||||
_deconstructItems.TryAdd(item, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unmarks an item for deconstruction (thread-safe).
|
|
||||||
/// </summary>
|
|
||||||
public static void UnmarkForDeconstruction(Item item)
|
|
||||||
{
|
|
||||||
_deconstructItems.TryRemove(item, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if an item is marked for deconstruction (thread-safe).
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsMarkedForDeconstruction(Item item)
|
|
||||||
{
|
|
||||||
return _deconstructItems.ContainsKey(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears all item collections (thread-safe). Used during unloading.
|
|
||||||
/// </summary>
|
|
||||||
public static void ClearAllItemCollections()
|
|
||||||
{
|
|
||||||
_itemDictionary.Clear();
|
|
||||||
_dangerousItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
_repairableItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
_cleanableItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
_sonarVisibleItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
_turretTargetItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
_chairItems = ImmutableHashSet<Item>.Empty;
|
|
||||||
_deconstructItems.Clear();
|
|
||||||
while (_pendingConditionUpdates.TryDequeue(out _)) { }
|
|
||||||
_cachedItemList = null;
|
|
||||||
_cachedItemListVersion = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cached item list for indexed access (used by AI systems)
|
|
||||||
private static volatile List<Item> _cachedItemList;
|
|
||||||
private static volatile int _cachedItemListVersion = -1;
|
|
||||||
private static volatile int _itemListVersion;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a cached list snapshot of all items for indexed access.
|
|
||||||
/// The list is refreshed when items are added or removed.
|
|
||||||
/// Thread-safe but may return slightly stale data.
|
|
||||||
/// </summary>
|
|
||||||
public static List<Item> GetCachedItemList()
|
|
||||||
{
|
|
||||||
int currentVersion = _itemListVersion;
|
|
||||||
if (_cachedItemList == null || _cachedItemListVersion != currentVersion)
|
|
||||||
{
|
|
||||||
_cachedItemList = _itemDictionary.Values.ToList();
|
|
||||||
_cachedItemListVersion = currentVersion;
|
|
||||||
}
|
|
||||||
return _cachedItemList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when items are added or removed to invalidate the cached list.
|
|
||||||
/// </summary>
|
|
||||||
private static void InvalidateCachedItemList()
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref _itemListVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public new ItemPrefab Prefab => base.Prefab as ItemPrefab;
|
public new ItemPrefab Prefab => base.Prefab as ItemPrefab;
|
||||||
@@ -296,12 +179,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private bool transformDirty = true;
|
private bool transformDirty = true;
|
||||||
|
|
||||||
private static readonly ConcurrentQueue<Item> _pendingConditionUpdates = new ConcurrentQueue<Item>();
|
private static readonly List<Item> itemsWithPendingConditionUpdates = new List<Item>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Flag to avoid duplicate enqueue for pending condition updates.
|
|
||||||
/// </summary>
|
|
||||||
private volatile bool _hasPendingConditionUpdate;
|
|
||||||
|
|
||||||
private float lastSentCondition;
|
private float lastSentCondition;
|
||||||
private float sendConditionUpdateTimer;
|
private float sendConditionUpdateTimer;
|
||||||
@@ -967,11 +845,11 @@ namespace Barotrauma
|
|||||||
isDangerous = value;
|
isDangerous = value;
|
||||||
if (!value)
|
if (!value)
|
||||||
{
|
{
|
||||||
RemoveFromImmutableSet(ref _dangerousItems, this);
|
_dangerousItems.Remove(this);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddToImmutableSet(ref _dangerousItems, this);
|
_dangerousItems.Add(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1520,13 +1398,12 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
InsertToList();
|
InsertToList();
|
||||||
_itemDictionary.TryAdd(ID, this);
|
ItemList.Add(this);
|
||||||
InvalidateCachedItemList();
|
if (Prefab.IsDangerous) { _dangerousItems.Add(this); }
|
||||||
if (Prefab.IsDangerous) { AddToImmutableSet(ref _dangerousItems, this); }
|
if (Repairables.Any()) { _repairableItems.Add(this); }
|
||||||
if (Repairables.Any()) { AddToImmutableSet(ref _repairableItems, this); }
|
if (Prefab.SonarSize > 0.0f) { _sonarVisibleItems.Add(this); }
|
||||||
if (Prefab.SonarSize > 0.0f) { AddToImmutableSet(ref _sonarVisibleItems, this); }
|
if (Prefab.IsAITurretTarget) { _turretTargetItems.Add(this); }
|
||||||
if (Prefab.IsAITurretTarget) { AddToImmutableSet(ref _turretTargetItems, this); }
|
if (Prefab.Tags.Contains(Barotrauma.Tags.ChairItem)) { _chairItems.Add(this); }
|
||||||
if (Prefab.Tags.Contains(Barotrauma.Tags.ChairItem)) { AddToImmutableSet(ref _chairItems, this); }
|
|
||||||
CheckCleanable();
|
CheckCleanable();
|
||||||
|
|
||||||
DebugConsole.Log("Created " + Name + " (" + ID + ")");
|
DebugConsole.Log("Created " + Name + " (" + ID + ")");
|
||||||
@@ -1822,13 +1699,7 @@ namespace Barotrauma
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
// Defer physics operation if in parallel context (Farseer is not thread-safe)
|
body.SetTransformIgnoreContacts(simPosition, rotation, setPrevTransform);
|
||||||
var capturedBody = body;
|
|
||||||
var capturedSimPos = simPosition;
|
|
||||||
var capturedRotation = rotation;
|
|
||||||
var capturedSetPrevTransform = setPrevTransform;
|
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() =>
|
|
||||||
capturedBody.SetTransformIgnoreContacts(capturedSimPos, capturedRotation, capturedSetPrevTransform));
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -1885,11 +1756,14 @@ namespace Barotrauma
|
|||||||
Prefab.PreferredContainers.Any() &&
|
Prefab.PreferredContainers.Any() &&
|
||||||
(container == null || container.HasTag(Barotrauma.Tags.AllowCleanup)))
|
(container == null || container.HasTag(Barotrauma.Tags.AllowCleanup)))
|
||||||
{
|
{
|
||||||
AddToImmutableSet(ref _cleanableItems, this);
|
if (!_cleanableItems.Contains(this))
|
||||||
|
{
|
||||||
|
_cleanableItems.Add(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RemoveFromImmutableSet(ref _cleanableItems, this);
|
_cleanableItems.Remove(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1905,19 +1779,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (ItemList != null && body != null)
|
if (ItemList != null && body != null)
|
||||||
{
|
{
|
||||||
// Defer physics operation if in parallel context (Farseer is not thread-safe)
|
|
||||||
var capturedBody = body;
|
|
||||||
var capturedNewPos = body.SimPosition + ConvertUnits.ToSimUnits(amount);
|
|
||||||
var capturedRotation = body.Rotation;
|
|
||||||
if (ignoreContacts)
|
if (ignoreContacts)
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() =>
|
body.SetTransformIgnoreContacts(body.SimPosition + ConvertUnits.ToSimUnits(amount), body.Rotation);
|
||||||
capturedBody.SetTransformIgnoreContacts(capturedNewPos, capturedRotation));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() =>
|
body.SetTransform(body.SimPosition + ConvertUnits.ToSimUnits(amount), body.Rotation);
|
||||||
capturedBody.SetTransform(capturedNewPos, capturedRotation));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (ItemComponent ic in components)
|
foreach (ItemComponent ic in components)
|
||||||
@@ -2426,10 +2294,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
needsConditionUpdate = true;
|
needsConditionUpdate = true;
|
||||||
}
|
}
|
||||||
if (needsConditionUpdate && !_hasPendingConditionUpdate)
|
if (needsConditionUpdate && !itemsWithPendingConditionUpdates.Contains(this))
|
||||||
{
|
{
|
||||||
_hasPendingConditionUpdate = true;
|
itemsWithPendingConditionUpdates.Add(this);
|
||||||
_pendingConditionUpdates.Enqueue(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2453,11 +2320,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (c.IsPower)
|
if (c.IsPower)
|
||||||
{
|
{
|
||||||
Powered.MarkConnectionChanged(c);
|
Powered.ChangedConnections.Add(c);
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
foreach (Connection conn in c.Recipients)
|
||||||
foreach (Connection conn in c.Recipients.ToList())
|
|
||||||
{
|
{
|
||||||
Powered.MarkConnectionChanged(conn);
|
Powered.ChangedConnections.Add(conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2486,9 +2352,9 @@ namespace Barotrauma
|
|||||||
public void SendPendingNetworkUpdates()
|
public void SendPendingNetworkUpdates()
|
||||||
{
|
{
|
||||||
if (!(GameMain.NetworkMember is { IsServer: true })) { return; }
|
if (!(GameMain.NetworkMember is { IsServer: true })) { return; }
|
||||||
if (!_hasPendingConditionUpdate) { return; }
|
if (!itemsWithPendingConditionUpdates.Contains(this)) { return; }
|
||||||
SendPendingNetworkUpdatesInternal();
|
SendPendingNetworkUpdatesInternal();
|
||||||
_hasPendingConditionUpdate = false;
|
itemsWithPendingConditionUpdates.Remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendPendingNetworkUpdatesInternal()
|
private void SendPendingNetworkUpdatesInternal()
|
||||||
@@ -2517,35 +2383,21 @@ namespace Barotrauma
|
|||||||
public static void UpdatePendingConditionUpdates(float deltaTime)
|
public static void UpdatePendingConditionUpdates(float deltaTime)
|
||||||
{
|
{
|
||||||
if (GameMain.NetworkMember is not { IsServer: true }) { return; }
|
if (GameMain.NetworkMember is not { IsServer: true }) { return; }
|
||||||
|
for (int i = 0; i < itemsWithPendingConditionUpdates.Count; i++)
|
||||||
int count = _pendingConditionUpdates.Count;
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
{
|
||||||
if (!_pendingConditionUpdates.TryDequeue(out var item)) { break; }
|
var item = itemsWithPendingConditionUpdates[i];
|
||||||
|
|
||||||
if (item == null || item.Removed)
|
if (item == null || item.Removed)
|
||||||
{
|
{
|
||||||
item._hasPendingConditionUpdate = false;
|
itemsWithPendingConditionUpdates.RemoveAt(i--);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.Submarine is { Loading: true })
|
|
||||||
{
|
|
||||||
// Re-enqueue, still loading
|
|
||||||
_pendingConditionUpdates.Enqueue(item);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (item.Submarine is { Loading: true }) { continue; }
|
||||||
|
|
||||||
item.sendConditionUpdateTimer -= deltaTime;
|
item.sendConditionUpdateTimer -= deltaTime;
|
||||||
if (item.sendConditionUpdateTimer <= 0.0f)
|
if (item.sendConditionUpdateTimer <= 0.0f)
|
||||||
{
|
{
|
||||||
item.SendPendingNetworkUpdatesInternal();
|
item.SendPendingNetworkUpdatesInternal();
|
||||||
item._hasPendingConditionUpdate = false;
|
itemsWithPendingConditionUpdates.RemoveAt(i--);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Not ready yet, re-enqueue
|
|
||||||
_pendingConditionUpdates.Enqueue(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2575,12 +2427,7 @@ namespace Barotrauma
|
|||||||
if (item != this)
|
if (item != this)
|
||||||
{
|
{
|
||||||
item.body.Enabled = false;
|
item.body.Enabled = false;
|
||||||
// Defer physics operation if in parallel context (Farseer is not thread-safe)
|
item.body.SetTransformIgnoreContacts(this.SimPosition, body.Rotation);
|
||||||
var capturedItemBody = item.body;
|
|
||||||
var capturedSimPos = this.SimPosition;
|
|
||||||
var capturedRotation = body.Rotation;
|
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() =>
|
|
||||||
capturedItemBody.SetTransformIgnoreContacts(capturedSimPos, capturedRotation));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2772,25 +2619,17 @@ namespace Barotrauma
|
|||||||
FindHull();
|
FindHull();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer physics transform operations if in parallel context.
|
|
||||||
// Farseer's DynamicTree is not thread-safe.
|
|
||||||
if (Submarine == null && prevSub != null)
|
if (Submarine == null && prevSub != null)
|
||||||
{
|
{
|
||||||
Vector2 newPos = body.SimPosition + prevSub.SimPosition;
|
body.SetTransformIgnoreContacts(body.SimPosition + prevSub.SimPosition, body.Rotation);
|
||||||
float rotation = body.Rotation;
|
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => body.SetTransformIgnoreContacts(newPos, rotation));
|
|
||||||
}
|
}
|
||||||
else if (Submarine != null && prevSub == null)
|
else if (Submarine != null && prevSub == null)
|
||||||
{
|
{
|
||||||
Vector2 newPos = body.SimPosition - Submarine.SimPosition;
|
body.SetTransformIgnoreContacts(body.SimPosition - Submarine.SimPosition, body.Rotation);
|
||||||
float rotation = body.Rotation;
|
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => body.SetTransformIgnoreContacts(newPos, rotation));
|
|
||||||
}
|
}
|
||||||
else if (Submarine != null && prevSub != null && Submarine != prevSub)
|
else if (Submarine != null && prevSub != null && Submarine != prevSub)
|
||||||
{
|
{
|
||||||
Vector2 newPos = body.SimPosition + prevSub.SimPosition - Submarine.SimPosition;
|
body.SetTransformIgnoreContacts(body.SimPosition + prevSub.SimPosition - Submarine.SimPosition, body.Rotation);
|
||||||
float rotation = body.Rotation;
|
|
||||||
PhysicsBodyQueue.ExecuteOrDefer(() => body.SetTransformIgnoreContacts(newPos, rotation));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Submarine != prevSub)
|
if (Submarine != prevSub)
|
||||||
@@ -3006,8 +2845,7 @@ namespace Barotrauma
|
|||||||
foreach (Connection c in connectionPanel.Connections)
|
foreach (Connection c in connectionPanel.Connections)
|
||||||
{
|
{
|
||||||
if (connectionFilter != null && !connectionFilter(c)) { continue; }
|
if (connectionFilter != null && !connectionFilter(c)) { continue; }
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
foreach (Connection recipient in c.Recipients)
|
||||||
foreach (Connection recipient in c.Recipients.ToList())
|
|
||||||
{
|
{
|
||||||
var component = recipient.Item.GetComponent<T>();
|
var component = recipient.Item.GetComponent<T>();
|
||||||
if (component != null)
|
if (component != null)
|
||||||
@@ -3040,8 +2878,7 @@ namespace Barotrauma
|
|||||||
foreach (Connection c in connectionPanel.Connections)
|
foreach (Connection c in connectionPanel.Connections)
|
||||||
{
|
{
|
||||||
if (connectionFilter != null && !connectionFilter(c)) { continue; }
|
if (connectionFilter != null && !connectionFilter(c)) { continue; }
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
foreach (Connection recipient in c.Recipients)
|
||||||
foreach (Connection recipient in c.Recipients.ToList())
|
|
||||||
{
|
{
|
||||||
var component = recipient.Item.GetComponent<T>();
|
var component = recipient.Item.GetComponent<T>();
|
||||||
if (component != null && !connectedComponents.Contains(component))
|
if (component != null && !connectedComponents.Contains(component))
|
||||||
@@ -3095,13 +2932,12 @@ namespace Barotrauma
|
|||||||
alreadySearched.Add(c);
|
alreadySearched.Add(c);
|
||||||
static IEnumerable<Connection> GetRecipients(Connection c)
|
static IEnumerable<Connection> GetRecipients(Connection c)
|
||||||
{
|
{
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
foreach (Connection recipient in c.Recipients)
|
||||||
foreach (Connection recipient in c.Recipients.ToList())
|
|
||||||
{
|
{
|
||||||
yield return recipient;
|
yield return recipient;
|
||||||
}
|
}
|
||||||
//check circuit box inputs/outputs this connection is connected to
|
//check circuit box inputs/outputs this connection is connected to
|
||||||
foreach (var circuitBoxConnection in c.CircuitBoxConnections.ToArray())
|
foreach (var circuitBoxConnection in c.CircuitBoxConnections)
|
||||||
{
|
{
|
||||||
yield return circuitBoxConnection.Connection;
|
yield return circuitBoxConnection.Connection;
|
||||||
}
|
}
|
||||||
@@ -3241,8 +3077,7 @@ namespace Barotrauma
|
|||||||
if (signal.stepsTaken > 5 && signal.source != null)
|
if (signal.stepsTaken > 5 && signal.source != null)
|
||||||
{
|
{
|
||||||
int duplicateRecipients = 0;
|
int duplicateRecipients = 0;
|
||||||
// Use ToList() snapshot for thread-safe iteration
|
foreach (var recipient in signal.source.LastSentSignalRecipients)
|
||||||
foreach (var recipient in signal.source.LastSentSignalRecipients.ToList())
|
|
||||||
{
|
{
|
||||||
if (recipient == connection)
|
if (recipient == connection)
|
||||||
{
|
{
|
||||||
@@ -3694,45 +3529,20 @@ namespace Barotrauma
|
|||||||
if (body != null)
|
if (body != null)
|
||||||
{
|
{
|
||||||
IsActive = true;
|
IsActive = true;
|
||||||
|
body.Enabled = true;
|
||||||
// Physics body operations must be deferred if we're in a parallel update context,
|
body.PhysEnabled = true;
|
||||||
// because Farseer Physics is not thread-safe.
|
body.ResetDynamics();
|
||||||
if (PhysicsBodyQueue.IsInParallelContext)
|
if (dropper != null)
|
||||||
{
|
{
|
||||||
// Capture the values we need for the deferred operation
|
if (body.Removed)
|
||||||
var capturedBody = body;
|
|
||||||
var capturedDropperSimPos = dropper?.SimPosition ?? Microsoft.Xna.Framework.Vector2.Zero;
|
|
||||||
var capturedSetTransform = setTransform && dropper != null;
|
|
||||||
|
|
||||||
PhysicsBodyQueue.Enqueue(() =>
|
|
||||||
{
|
{
|
||||||
if (capturedBody.Removed || Removed) { return; }
|
DebugConsole.ThrowError(
|
||||||
capturedBody.Enabled = true;
|
"Failed to drop the item \"" + Name + "\" (body has been removed"
|
||||||
capturedBody.PhysEnabled = true;
|
+ (Removed ? ", item has been removed)" : ")"));
|
||||||
capturedBody.ResetDynamics();
|
}
|
||||||
if (capturedSetTransform)
|
else if (setTransform)
|
||||||
{
|
|
||||||
capturedBody.SetTransformIgnoreContacts(capturedDropperSimPos, 0.0f);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
body.Enabled = true;
|
|
||||||
body.PhysEnabled = true;
|
|
||||||
body.ResetDynamics();
|
|
||||||
if (dropper != null)
|
|
||||||
{
|
{
|
||||||
if (body.Removed)
|
body.SetTransformIgnoreContacts(dropper.SimPosition, 0.0f);
|
||||||
{
|
|
||||||
DebugConsole.ThrowError(
|
|
||||||
"Failed to drop the item \"" + Name + "\" (body has been removed"
|
|
||||||
+ (Removed ? ", item has been removed)" : ")"));
|
|
||||||
}
|
|
||||||
else if (setTransform)
|
|
||||||
{
|
|
||||||
body.SetTransformIgnoreContacts(dropper.SimPosition, 0.0f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3743,22 +3553,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (setTransform)
|
if (setTransform)
|
||||||
{
|
{
|
||||||
// Defer SetTransform if in parallel context
|
SetTransform(Container.SimPosition, 0.0f);
|
||||||
if (PhysicsBodyQueue.IsInParallelContext)
|
|
||||||
{
|
|
||||||
var capturedContainerSimPos = Container.SimPosition;
|
|
||||||
PhysicsBodyQueue.Enqueue(() =>
|
|
||||||
{
|
|
||||||
if (!Removed)
|
|
||||||
{
|
|
||||||
SetTransform(capturedContainerSimPos, 0.0f);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SetTransform(Container.SimPosition, 0.0f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Container.RemoveContained(this);
|
Container.RemoveContained(this);
|
||||||
Container = null;
|
Container = null;
|
||||||
@@ -4422,7 +4217,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.GetAttributeBool("markedfordeconstruction", false)) { _deconstructItems.TryAdd(item, 0); }
|
if (element.GetAttributeBool("markedfordeconstruction", false)) { _deconstructItems.Add(item); }
|
||||||
|
|
||||||
float prevRotation = item.Rotation;
|
float prevRotation = item.Rotation;
|
||||||
if (element.GetAttributeBool("flippedx", false)) { item.FlipX(relativeToSub: false, force: true); }
|
if (element.GetAttributeBool("flippedx", false)) { item.FlipX(relativeToSub: false, force: true); }
|
||||||
@@ -4715,7 +4510,7 @@ namespace Barotrauma
|
|||||||
new XAttribute("name", Prefab.OriginalName),
|
new XAttribute("name", Prefab.OriginalName),
|
||||||
new XAttribute("identifier", Prefab.Identifier),
|
new XAttribute("identifier", Prefab.Identifier),
|
||||||
new XAttribute("ID", ID),
|
new XAttribute("ID", ID),
|
||||||
new XAttribute("markedfordeconstruction", _deconstructItems.ContainsKey(this)));
|
new XAttribute("markedfordeconstruction", _deconstructItems.Contains(this)));
|
||||||
|
|
||||||
if (PendingItemSwap != null)
|
if (PendingItemSwap != null)
|
||||||
{
|
{
|
||||||
@@ -4918,16 +4713,14 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void RemoveFromLists()
|
private void RemoveFromLists()
|
||||||
{
|
{
|
||||||
_itemDictionary.TryRemove(ID, out _);
|
ItemList.Remove(this);
|
||||||
InvalidateCachedItemList();
|
_dangerousItems.Remove(this);
|
||||||
RemoveFromImmutableSet(ref _dangerousItems, this);
|
_repairableItems.Remove(this);
|
||||||
RemoveFromImmutableSet(ref _repairableItems, this);
|
_sonarVisibleItems.Remove(this);
|
||||||
RemoveFromImmutableSet(ref _sonarVisibleItems, this);
|
_cleanableItems.Remove(this);
|
||||||
RemoveFromImmutableSet(ref _cleanableItems, this);
|
_deconstructItems.Remove(this);
|
||||||
_deconstructItems.TryRemove(this, out _);
|
_turretTargetItems.Remove(this);
|
||||||
RemoveFromImmutableSet(ref _turretTargetItems, this);
|
_chairItems.Remove(this);
|
||||||
RemoveFromImmutableSet(ref _chairItems, this);
|
|
||||||
_hasPendingConditionUpdate = false;
|
|
||||||
RemoveFromDroppedStack(allowClientExecute: true);
|
RemoveFromDroppedStack(allowClientExecute: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
@@ -15,55 +14,6 @@ using Microsoft.Xna.Framework;
|
|||||||
|
|
||||||
namespace Barotrauma.MapCreatures.Behavior
|
namespace Barotrauma.MapCreatures.Behavior
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for BallastFloraBehavior list operations.
|
|
||||||
/// Uses copy-on-write pattern for lock-free reads.
|
|
||||||
/// </summary>
|
|
||||||
internal class ThreadSafeBallastFloraList : IEnumerable<BallastFloraBehavior>
|
|
||||||
{
|
|
||||||
private volatile List<BallastFloraBehavior> _list = new List<BallastFloraBehavior>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(BallastFloraBehavior entity)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<BallastFloraBehavior>(_list) { entity };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(BallastFloraBehavior entity)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<BallastFloraBehavior>(_list);
|
|
||||||
bool removed = newList.Remove(entity);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<BallastFloraBehavior>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<BallastFloraBehavior> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// LINQ-friendly methods
|
|
||||||
public List<BallastFloraBehavior> ToList() => new List<BallastFloraBehavior>(_list);
|
|
||||||
public bool Any() => _list.Any();
|
|
||||||
public bool Any(Func<BallastFloraBehavior, bool> predicate) => _list.Any(predicate);
|
|
||||||
public IEnumerable<BallastFloraBehavior> Where(Func<BallastFloraBehavior, bool> predicate) => _list.Where(predicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
class BallastFloraBranch : VineTile
|
class BallastFloraBranch : VineTile
|
||||||
{
|
{
|
||||||
public readonly BallastFloraBehavior? ParentBallastFlora;
|
public readonly BallastFloraBehavior? ParentBallastFlora;
|
||||||
@@ -182,7 +132,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
|||||||
public List<Tuple<Vector2, Vector2>> debugSearchLines = new List<Tuple<Vector2, Vector2>>();
|
public List<Tuple<Vector2, Vector2>> debugSearchLines = new List<Tuple<Vector2, Vector2>>();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private readonly static ThreadSafeBallastFloraList _entityList = new ThreadSafeBallastFloraList();
|
private readonly static List<BallastFloraBehavior> _entityList = new List<BallastFloraBehavior>();
|
||||||
public static IEnumerable<BallastFloraBehavior> EntityList => _entityList;
|
public static IEnumerable<BallastFloraBehavior> EntityList => _entityList;
|
||||||
|
|
||||||
public enum NetworkHeader
|
public enum NetworkHeader
|
||||||
@@ -357,12 +307,6 @@ namespace Barotrauma.MapCreatures.Behavior
|
|||||||
public readonly List<BallastFloraBranch> Branches = new List<BallastFloraBranch>();
|
public readonly List<BallastFloraBranch> Branches = new List<BallastFloraBranch>();
|
||||||
private BallastFloraBranch? root;
|
private BallastFloraBranch? root;
|
||||||
private readonly List<Body> bodies = new List<Body>();
|
private readonly List<Body> bodies = new List<Body>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Branches that need physics bodies created on the main thread.
|
|
||||||
/// </summary>
|
|
||||||
private readonly List<BallastFloraBranch> pendingBodyCreations = new List<BallastFloraBranch>();
|
|
||||||
private readonly object pendingBodyCreationsLock = new object();
|
|
||||||
|
|
||||||
private bool isDead;
|
private bool isDead;
|
||||||
|
|
||||||
@@ -403,8 +347,7 @@ namespace Barotrauma.MapCreatures.Behavior
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
UpdateConnections(branch);
|
UpdateConnections(branch);
|
||||||
// OnMapLoaded runs on the main thread, so we can create bodies immediately
|
CreateBody(branch);
|
||||||
CreateBody(branch, immediate: true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1055,52 +998,10 @@ namespace Barotrauma.MapCreatures.Behavior
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queue a physics body creation for a branch.
|
/// Create a body for a branch which works as the hitbox for flamer
|
||||||
/// The actual body will be created on the main thread to ensure thread safety.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="branch">The branch to create a body for</param>
|
/// <param name="branch"></param>
|
||||||
/// <param name="immediate">If true, create the body immediately (only safe when called from main thread)</param>
|
private void CreateBody(BallastFloraBranch branch)
|
||||||
private void CreateBody(BallastFloraBranch branch, bool immediate = false)
|
|
||||||
{
|
|
||||||
if (immediate)
|
|
||||||
{
|
|
||||||
CreateBodyImmediate(branch);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (pendingBodyCreationsLock)
|
|
||||||
{
|
|
||||||
pendingBodyCreations.Add(branch);
|
|
||||||
}
|
|
||||||
PhysicsBodyQueue.EnqueueCreation(() => ProcessPendingBodyCreations());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Process all pending body creations on the main thread.
|
|
||||||
/// This ensures Farseer Physics operations are thread-safe.
|
|
||||||
/// </summary>
|
|
||||||
private void ProcessPendingBodyCreations()
|
|
||||||
{
|
|
||||||
List<BallastFloraBranch> branchesToProcess;
|
|
||||||
lock (pendingBodyCreationsLock)
|
|
||||||
{
|
|
||||||
if (pendingBodyCreations.Count == 0) { return; }
|
|
||||||
branchesToProcess = new List<BallastFloraBranch>(pendingBodyCreations);
|
|
||||||
pendingBodyCreations.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var branch in branchesToProcess)
|
|
||||||
{
|
|
||||||
if (branch.Removed) { continue; }
|
|
||||||
CreateBodyImmediate(branch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Actually create the physics body for a branch.
|
|
||||||
/// Must be called on the main thread.
|
|
||||||
/// </summary>
|
|
||||||
private void CreateBodyImmediate(BallastFloraBranch branch)
|
|
||||||
{
|
{
|
||||||
Rectangle rect = branch.Rect;
|
Rectangle rect = branch.Rect;
|
||||||
Vector2 pos = Parent.Position + Offset + branch.Position;
|
Vector2 pos = Parent.Position + Offset + branch.Position;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Barotrauma.IO;
|
using Barotrauma.IO;
|
||||||
@@ -21,10 +20,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public const ushort MaxEntityCount = ushort.MaxValue - 4; //ushort.MaxValue - 4 because the 4 values above are reserved values
|
public const ushort MaxEntityCount = ushort.MaxValue - 4; //ushort.MaxValue - 4 because the 4 values above are reserved values
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<ushort, Entity> dictionary = new ConcurrentDictionary<ushort, Entity>();
|
private static readonly Dictionary<ushort, Entity> dictionary = new Dictionary<ushort, Entity>();
|
||||||
public static IReadOnlyCollection<Entity> GetEntities()
|
public static IReadOnlyCollection<Entity> GetEntities()
|
||||||
{
|
{
|
||||||
return (IReadOnlyCollection<Entity>)dictionary.Values;
|
return dictionary.Values;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int EntityCount => dictionary.Count;
|
public static int EntityCount => dictionary.Count;
|
||||||
@@ -123,11 +122,13 @@ namespace Barotrauma
|
|||||||
//give a unique ID
|
//give a unique ID
|
||||||
ID = DetermineID(id, submarine);
|
ID = DetermineID(id, submarine);
|
||||||
|
|
||||||
if (!dictionary.TryAdd(ID, this))
|
if (dictionary.ContainsKey(ID))
|
||||||
{
|
{
|
||||||
throw new Exception($"ID {ID} is taken by {dictionary[ID]}");
|
throw new Exception($"ID {ID} is taken by {dictionary[ID]}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dictionary.Add(ID, this);
|
||||||
|
|
||||||
CreationStackTrace = "";
|
CreationStackTrace = "";
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
var st = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
|
var st = new StackTrace(skipFrames: 2, fNeedFileInfo: true);
|
||||||
@@ -146,6 +147,7 @@ namespace Barotrauma
|
|||||||
CreationStackTrace += $"{fileName}@{fileLineNumber}; ";
|
CreationStackTrace += $"{fileName}@{fileLineNumber}; ";
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#warning TODO: consider removing this mutex, entity creation probably shouldn't be multithreaded
|
||||||
lock (creationCounterMutex)
|
lock (creationCounterMutex)
|
||||||
{
|
{
|
||||||
CreationIndex = creationCounter;
|
CreationIndex = creationCounter;
|
||||||
@@ -259,7 +261,7 @@ namespace Barotrauma
|
|||||||
DebugConsole.ThrowError($"Error while removing item \"{item}\"", exception);
|
DebugConsole.ThrowError($"Error while removing item \"{item}\"", exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item.ClearAllItemCollections();
|
Item.ItemList.Clear();
|
||||||
}
|
}
|
||||||
if (Character.CharacterList.Count > 0)
|
if (Character.CharacterList.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -323,7 +325,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dictionary.TryRemove(ID, out _);
|
dictionary.Remove(ID);
|
||||||
}
|
}
|
||||||
IdFreed = true;
|
IdFreed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using Microsoft.Xna.Framework;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -649,11 +648,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThreadLocal for thread-safe structure damage tracking
|
private static readonly Dictionary<Structure, float> damagedStructures = new Dictionary<Structure, float>();
|
||||||
private static readonly ThreadLocal<Dictionary<Structure, float>> damagedStructuresLocal =
|
|
||||||
new ThreadLocal<Dictionary<Structure, float>>(() => new Dictionary<Structure, float>());
|
|
||||||
private static Dictionary<Structure, float> damagedStructures => damagedStructuresLocal.Value;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
|
/// Returns a dictionary where the keys are the structures that took damage and the values are the amount of damage taken
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -8,71 +8,13 @@ using System;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for Gap list operations.
|
|
||||||
/// Uses copy-on-write pattern for lock-free reads.
|
|
||||||
/// </summary>
|
|
||||||
internal class ThreadSafeGapList : IEnumerable<Gap>
|
|
||||||
{
|
|
||||||
private volatile List<Gap> _list = new List<Gap>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(Gap gap)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Gap>(_list) { gap };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(Gap gap)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Gap>(_list);
|
|
||||||
bool removed = newList.Remove(gap);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<Gap>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(Gap gap) => _list.Contains(gap);
|
|
||||||
|
|
||||||
public Gap this[int index] => _list[index];
|
|
||||||
|
|
||||||
public IEnumerator<Gap> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// LINQ-friendly methods
|
|
||||||
public List<Gap> ToList() => new List<Gap>(_list);
|
|
||||||
public Gap FirstOrDefault(Func<Gap, bool> predicate) => _list.FirstOrDefault(predicate);
|
|
||||||
public Gap Find(Predicate<Gap> predicate) => _list.Find(predicate);
|
|
||||||
public List<Gap> FindAll(Predicate<Gap> predicate) => _list.FindAll(predicate);
|
|
||||||
public IEnumerable<Gap> Where(Func<Gap, bool> predicate) => _list.Where(predicate);
|
|
||||||
public bool Any() => _list.Any();
|
|
||||||
public bool Any(Func<Gap, bool> predicate) => _list.Any(predicate);
|
|
||||||
public IOrderedEnumerable<Gap> OrderBy<TKey>(Func<Gap, TKey> keySelector) => _list.OrderBy(keySelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
partial class Gap : MapEntity, ISerializableEntity
|
partial class Gap : MapEntity, ISerializableEntity
|
||||||
{
|
{
|
||||||
public static ThreadSafeGapList GapList = new ThreadSafeGapList();
|
public static List<Gap> GapList = new List<Gap>();
|
||||||
|
|
||||||
const float MaxFlowForce = 500.0f;
|
const float MaxFlowForce = 500.0f;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Barotrauma.MapCreatures.Behavior;
|
using Barotrauma.MapCreatures.Behavior;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
@@ -14,116 +13,6 @@ using Barotrauma.Extensions;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for Hull list operations.
|
|
||||||
/// Uses copy-on-write pattern for lock-free reads.
|
|
||||||
/// </summary>
|
|
||||||
internal class ThreadSafeHullList : IEnumerable<Hull>
|
|
||||||
{
|
|
||||||
private volatile List<Hull> _list = new List<Hull>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(Hull hull)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Hull>(_list) { hull };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(Hull hull)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Hull>(_list);
|
|
||||||
bool removed = newList.Remove(hull);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<Hull>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(Hull hull) => _list.Contains(hull);
|
|
||||||
|
|
||||||
public Hull this[int index] => _list[index];
|
|
||||||
|
|
||||||
public IEnumerator<Hull> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// LINQ-friendly methods
|
|
||||||
public List<Hull> ToList() => new List<Hull>(_list);
|
|
||||||
public Hull FirstOrDefault(Func<Hull, bool> predicate) => _list.FirstOrDefault(predicate);
|
|
||||||
public Hull Find(Predicate<Hull> predicate) => _list.Find(predicate);
|
|
||||||
public List<Hull> FindAll(Predicate<Hull> predicate) => _list.FindAll(predicate);
|
|
||||||
public IEnumerable<Hull> Where(Func<Hull, bool> predicate) => _list.Where(predicate);
|
|
||||||
public bool Any() => _list.Any();
|
|
||||||
public bool Any(Func<Hull, bool> predicate) => _list.Any(predicate);
|
|
||||||
public bool Exists(Predicate<Hull> predicate) => _list.Exists(predicate);
|
|
||||||
public void ForEach(Action<Hull> action) => _list.ForEach(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for EntityGrid list operations.
|
|
||||||
/// Uses copy-on-write pattern for lock-free reads.
|
|
||||||
/// </summary>
|
|
||||||
internal class ThreadSafeEntityGridList : IEnumerable<EntityGrid>
|
|
||||||
{
|
|
||||||
private volatile List<EntityGrid> _list = new List<EntityGrid>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(EntityGrid grid)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<EntityGrid>(_list) { grid };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(EntityGrid grid)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<EntityGrid>(_list);
|
|
||||||
bool removed = newList.Remove(grid);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<EntityGrid>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntityGrid this[int index] => _list[index];
|
|
||||||
|
|
||||||
public IEnumerator<EntityGrid> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// LINQ-friendly methods
|
|
||||||
public List<EntityGrid> ToList() => new List<EntityGrid>(_list);
|
|
||||||
public EntityGrid FirstOrDefault(Func<EntityGrid, bool> predicate) => _list.FirstOrDefault(predicate);
|
|
||||||
public EntityGrid Find(Predicate<EntityGrid> predicate) => _list.Find(predicate);
|
|
||||||
public IEnumerable<EntityGrid> Where(Func<EntityGrid, bool> predicate) => _list.Where(predicate);
|
|
||||||
public bool Any() => _list.Any();
|
|
||||||
}
|
|
||||||
|
|
||||||
partial class BackgroundSection
|
partial class BackgroundSection
|
||||||
{
|
{
|
||||||
public Rectangle Rect;
|
public Rectangle Rect;
|
||||||
@@ -224,8 +113,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial class Hull : MapEntity, ISerializableEntity, IServerSerializable
|
partial class Hull : MapEntity, ISerializableEntity, IServerSerializable
|
||||||
{
|
{
|
||||||
public readonly static ThreadSafeHullList HullList = new ThreadSafeHullList();
|
public readonly static List<Hull> HullList = new List<Hull>();
|
||||||
public readonly static ThreadSafeEntityGridList EntityGrids = new ThreadSafeEntityGridList();
|
public readonly static List<EntityGrid> EntityGrids = new List<EntityGrid>();
|
||||||
|
|
||||||
public static bool ShowHulls = true;
|
public static bool ShowHulls = true;
|
||||||
|
|
||||||
@@ -1244,18 +1133,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used in <see cref="GetApproximateDistance"/> - ThreadLocal for thread safety
|
/// Used in <see cref="GetApproximateDistance"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly ThreadLocal<Dictionary<Hull, float>> cachedDistancesLocal =
|
private static readonly Dictionary<Hull, float> cachedDistances = [];
|
||||||
new ThreadLocal<Dictionary<Hull, float>>(() => new Dictionary<Hull, float>());
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used in <see cref="GetApproximateDistance"/> - ThreadLocal for thread safety
|
/// Used in <see cref="GetApproximateDistance"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly ThreadLocal<PriorityQueue<(Hull hull, Vector2 pos), float>> priorityQueueLocal =
|
private static readonly PriorityQueue<(Hull hull, Vector2 pos), float> priorityQueue = new PriorityQueue<(Hull hull, Vector2 pos), float>();
|
||||||
new ThreadLocal<PriorityQueue<(Hull hull, Vector2 pos), float>>(() => new PriorityQueue<(Hull hull, Vector2 pos), float>());
|
|
||||||
|
|
||||||
private static Dictionary<Hull, float> cachedDistances => cachedDistancesLocal.Value;
|
|
||||||
private static PriorityQueue<(Hull hull, Vector2 pos), float> priorityQueue => priorityQueueLocal.Value;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
|
/// Approximate distance from this hull to the target hull, moving through open gaps without passing through walls.
|
||||||
|
|||||||
@@ -4775,7 +4775,7 @@ namespace Barotrauma
|
|||||||
// BeaconStation.FlipX();
|
// BeaconStation.FlipX();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Item sonarItem = Item.ItemList.FirstOrDefault(it => it.Submarine == BeaconStation && it.GetComponent<Sonar>() != null);
|
Item sonarItem = Item.ItemList.Find(it => it.Submarine == BeaconStation && it.GetComponent<Sonar>() != null);
|
||||||
if (sonarItem == null)
|
if (sonarItem == null)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"No sonar found in the beacon station \"{beaconStationName}\"!");
|
DebugConsole.ThrowError($"No sonar found in the beacon station \"{beaconStationName}\"!");
|
||||||
@@ -4794,7 +4794,7 @@ namespace Barotrauma
|
|||||||
throw new InvalidOperationException("Failed to prepare beacon station (no beacon station in the level).");
|
throw new InvalidOperationException("Failed to prepare beacon station (no beacon station in the level).");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Item> beaconItems = Item.ItemList.Where(it => it.Submarine == BeaconStation).ToList();
|
List<Item> beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation);
|
||||||
|
|
||||||
Item reactorItem = beaconItems.Find(it => it.GetComponent<Reactor>() != null);
|
Item reactorItem = beaconItems.Find(it => it.GetComponent<Reactor>() != null);
|
||||||
Reactor reactorComponent = null;
|
Reactor reactorComponent = null;
|
||||||
@@ -4840,7 +4840,7 @@ namespace Barotrauma
|
|||||||
if (BeaconStation?.Info?.BeaconStationInfo is { AllowDisconnectedWires: false }) { return; }
|
if (BeaconStation?.Info?.BeaconStationInfo is { AllowDisconnectedWires: false }) { return; }
|
||||||
|
|
||||||
if (disconnectWireProbability <= 0.0f) { return; }
|
if (disconnectWireProbability <= 0.0f) { return; }
|
||||||
List<Item> beaconItems = Item.ItemList.Where(it => it.Submarine == BeaconStation).ToList();
|
List<Item> beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation);
|
||||||
foreach (Item item in beaconItems.Where(it => it.GetComponent<Wire>() != null).ToList())
|
foreach (Item item in beaconItems.Where(it => it.GetComponent<Wire>() != null).ToList())
|
||||||
{
|
{
|
||||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||||
@@ -4878,7 +4878,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (breakDeviceProbability <= 0.0f) { return; }
|
if (breakDeviceProbability <= 0.0f) { return; }
|
||||||
//break powered items
|
//break powered items
|
||||||
List<Item> beaconItems = Item.ItemList.Where(it => it.Submarine == BeaconStation).ToList();
|
List<Item> beaconItems = Item.ItemList.FindAll(it => it.Submarine == BeaconStation);
|
||||||
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
|
foreach (Item item in beaconItems.Where(it => it.Components.Any(c => c is Powered) && it.Components.Any(c => c is Repairable)))
|
||||||
{
|
{
|
||||||
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
if (item.NonInteractable || item.InvulnerableToDamage) { continue; }
|
||||||
|
|||||||
@@ -1371,7 +1371,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (TakenItem takenItem in takenItems)
|
foreach (TakenItem takenItem in takenItems)
|
||||||
{
|
{
|
||||||
Item item = Item.ItemList.FirstOrDefault(it => takenItem.Matches(it));
|
Item item = Item.ItemList.Find(it => takenItem.Matches(it));
|
||||||
item?.Remove();
|
item?.Remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,111 +6,14 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for MapEntity list operations.
|
|
||||||
/// Uses copy-on-write pattern for lock-free reads.
|
|
||||||
/// </summary>
|
|
||||||
internal class ThreadSafeMapEntityList : IEnumerable<MapEntity>
|
|
||||||
{
|
|
||||||
private volatile List<MapEntity> _list = new List<MapEntity>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(MapEntity entity)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<MapEntity>(_list) { entity };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Insert(int index, MapEntity entity)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<MapEntity>(_list);
|
|
||||||
newList.Insert(index, entity);
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Atomically inserts an entity at a position determined by the insertAction.
|
|
||||||
/// The insertAction is executed within the lock to ensure thread-safety.
|
|
||||||
/// </summary>
|
|
||||||
public void InsertWithAction(MapEntity entity, Action<List<MapEntity>, MapEntity> insertAction)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<MapEntity>(_list);
|
|
||||||
insertAction(newList, entity);
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(MapEntity entity)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<MapEntity>(_list);
|
|
||||||
bool removed = newList.Remove(entity);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int RemoveAll(Predicate<MapEntity> match)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<MapEntity>(_list);
|
|
||||||
int count = newList.RemoveAll(match);
|
|
||||||
if (count > 0)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<MapEntity>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(MapEntity entity) => _list.Contains(entity);
|
|
||||||
|
|
||||||
public MapEntity this[int index] => _list[index];
|
|
||||||
|
|
||||||
public IEnumerator<MapEntity> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// LINQ-friendly methods that work on a snapshot
|
|
||||||
public List<MapEntity> ToList() => new List<MapEntity>(_list);
|
|
||||||
public MapEntity FirstOrDefault(Func<MapEntity, bool> predicate) => _list.FirstOrDefault(predicate);
|
|
||||||
public MapEntity Find(Predicate<MapEntity> predicate) => _list.Find(predicate);
|
|
||||||
public List<MapEntity> FindAll(Predicate<MapEntity> predicate) => _list.FindAll(predicate);
|
|
||||||
public IEnumerable<MapEntity> Where(Func<MapEntity, bool> predicate) => _list.Where(predicate);
|
|
||||||
public bool Any(Func<MapEntity, bool> predicate) => _list.Any(predicate);
|
|
||||||
public bool Exists(Predicate<MapEntity> predicate) => _list.Exists(predicate);
|
|
||||||
public IOrderedEnumerable<MapEntity> OrderBy<TKey>(Func<MapEntity, TKey> keySelector) => _list.OrderBy(keySelector);
|
|
||||||
public void ForEach(Action<MapEntity> action) => _list.ForEach(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract partial class MapEntity : Entity, ISpatialEntity
|
abstract partial class MapEntity : Entity, ISpatialEntity
|
||||||
{
|
{
|
||||||
public readonly static ThreadSafeMapEntityList MapEntityList = new ThreadSafeMapEntityList();
|
public readonly static List<MapEntity> MapEntityList = new List<MapEntity>();
|
||||||
|
|
||||||
public readonly MapEntityPrefab Prefab;
|
public readonly MapEntityPrefab Prefab;
|
||||||
|
|
||||||
@@ -656,51 +559,45 @@ namespace Barotrauma
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use atomic insertion to ensure thread-safety
|
//sort damageable walls by sprite depth:
|
||||||
MapEntityList.InsertWithAction(this, (list, entity) =>
|
//necessary because rendering the damage effect starts a new sprite batch and breaks the order otherwise
|
||||||
|
int i = 0;
|
||||||
|
if (this is Structure { DrawDamageEffect: true } structure)
|
||||||
{
|
{
|
||||||
int i = 0;
|
//insertion sort according to draw depth
|
||||||
|
float drawDepth = structure.SpriteDepth;
|
||||||
//sort damageable walls by sprite depth:
|
while (i < MapEntityList.Count)
|
||||||
//necessary because rendering the damage effect starts a new sprite batch and breaks the order otherwise
|
|
||||||
if (entity is Structure { DrawDamageEffect: true } structure)
|
|
||||||
{
|
{
|
||||||
//insertion sort according to draw depth
|
float otherDrawDepth = (MapEntityList[i] as Structure)?.SpriteDepth ?? 1.0f;
|
||||||
float drawDepth = structure.SpriteDepth;
|
if (otherDrawDepth < drawDepth) { break; }
|
||||||
while (i < list.Count)
|
i++;
|
||||||
{
|
}
|
||||||
float otherDrawDepth = (list[i] as Structure)?.SpriteDepth ?? 1.0f;
|
MapEntityList.Insert(i, this);
|
||||||
if (otherDrawDepth < drawDepth) { break; }
|
return;
|
||||||
i++;
|
}
|
||||||
}
|
|
||||||
list.Insert(i, entity);
|
i = 0;
|
||||||
|
while (i < MapEntityList.Count)
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
if (MapEntityList[i - 1]?.Prefab == Prefab)
|
||||||
|
{
|
||||||
|
MapEntityList.Insert(i, this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
i = 0;
|
|
||||||
var mapEntity = (MapEntity)entity;
|
|
||||||
while (i < list.Count)
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
if (list[i - 1]?.Prefab == mapEntity.Prefab)
|
|
||||||
{
|
|
||||||
list.Insert(i, entity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
i = 0;
|
i = 0;
|
||||||
while (i < list.Count)
|
while (i < MapEntityList.Count)
|
||||||
{
|
{
|
||||||
i++;
|
i++;
|
||||||
Sprite existingSprite = list[i - 1].Sprite;
|
Sprite existingSprite = MapEntityList[i - 1].Sprite;
|
||||||
if (existingSprite == null) { continue; }
|
if (existingSprite == null) { continue; }
|
||||||
if (existingSprite.Texture == mapEntity.Sprite?.Texture) { break; }
|
if (existingSprite.Texture == this.Sprite.Texture) { break; }
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
list.Insert(i, entity);
|
MapEntityList.Insert(i, this);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -763,31 +660,15 @@ namespace Barotrauma
|
|||||||
// basically nothing here is thread-safe so
|
// basically nothing here is thread-safe so
|
||||||
foreach(var hull in hullList)
|
foreach(var hull in hullList)
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.IsInParallelContext = true;
|
hull.Update(deltaTime, cam);
|
||||||
try
|
}
|
||||||
{
|
|
||||||
hull.Update(deltaTime, cam);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PhysicsBodyQueue.IsInParallelContext = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
// Structure parallel update
|
// Structure parallel update
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
Parallel.ForEach(structureList, parallelOptions, structure =>
|
Parallel.ForEach(structureList, parallelOptions, structure =>
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.IsInParallelContext = true;
|
structure.Update(deltaTime, cam);
|
||||||
try
|
|
||||||
{
|
|
||||||
structure.Update(deltaTime, cam);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PhysicsBodyQueue.IsInParallelContext = false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// Gap reset (must be done before update)
|
// Gap reset (must be done before update)
|
||||||
@@ -805,10 +686,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Process any physics operations queued during Hull/Structure updates.
|
|
||||||
// BallastFlora growth (from Hull.Update) may queue physics body creations/transforms.
|
|
||||||
PhysicsBodyQueue.ProcessPendingOperations();
|
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
// Hull Cheats need to be executed after Hull update
|
// Hull Cheats need to be executed after Hull update
|
||||||
Hull.UpdateCheats(deltaTime, cam);
|
Hull.UpdateCheats(deltaTime, cam);
|
||||||
@@ -818,19 +695,8 @@ namespace Barotrauma
|
|||||||
var shuffledGaps = gapList.OrderBy(g => Rand.Int(int.MaxValue)).ToList();
|
var shuffledGaps = gapList.OrderBy(g => Rand.Int(int.MaxValue)).ToList();
|
||||||
Parallel.ForEach(gapList, parallelOptions, gap =>
|
Parallel.ForEach(gapList, parallelOptions, gap =>
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.IsInParallelContext = true;
|
gap.Update(deltaTime, cam);
|
||||||
try
|
|
||||||
{
|
|
||||||
gap.Update(deltaTime, cam);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PhysicsBodyQueue.IsInParallelContext = false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process any physics operations queued during Gap updates.
|
|
||||||
PhysicsBodyQueue.ProcessPendingOperations();
|
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
@@ -846,19 +712,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Parallel.ForEach(itemList, parallelOptions, item =>
|
foreach (Item item in itemList)
|
||||||
{
|
{
|
||||||
PhysicsBodyQueue.IsInParallelContext = true;
|
lastUpdatedItem = item;
|
||||||
try
|
item.Update(scaledDeltaTime, cam);
|
||||||
{
|
}
|
||||||
lastUpdatedItem = item;
|
|
||||||
item.Update(scaledDeltaTime, cam);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PhysicsBodyQueue.IsInParallelContext = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException e)
|
catch (InvalidOperationException e)
|
||||||
{
|
{
|
||||||
@@ -869,10 +727,6 @@ namespace Barotrauma
|
|||||||
throw new InvalidOperationException($"Error while updating item {lastUpdatedItem?.Name ?? "null"}", innerException: e);
|
throw new InvalidOperationException($"Error while updating item {lastUpdatedItem?.Name ?? "null"}", innerException: e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process any physics operations that were queued during the parallel update.
|
|
||||||
// This must be done on the main thread because Farseer Physics is not thread-safe.
|
|
||||||
PhysicsBodyQueue.ProcessPendingOperations();
|
|
||||||
|
|
||||||
UpdateAllProjSpecific(scaledDeltaTime);
|
UpdateAllProjSpecific(scaledDeltaTime);
|
||||||
Spawner?.Update();
|
Spawner?.Update();
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using Microsoft.Xna.Framework;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using Barotrauma.Abilities;
|
using Barotrauma.Abilities;
|
||||||
@@ -19,63 +18,6 @@ using Barotrauma.Lights;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for Structure list operations.
|
|
||||||
/// Uses copy-on-write pattern for lock-free reads.
|
|
||||||
/// </summary>
|
|
||||||
internal class ThreadSafeStructureList : IEnumerable<Structure>
|
|
||||||
{
|
|
||||||
private volatile List<Structure> _list = new List<Structure>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(Structure structure)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Structure>(_list) { structure };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(Structure structure)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Structure>(_list);
|
|
||||||
bool removed = newList.Remove(structure);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<Structure>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(Structure structure) => _list.Contains(structure);
|
|
||||||
|
|
||||||
public Structure this[int index] => _list[index];
|
|
||||||
|
|
||||||
public IEnumerator<Structure> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// LINQ-friendly methods
|
|
||||||
public List<Structure> ToList() => new List<Structure>(_list);
|
|
||||||
public Structure FirstOrDefault(Func<Structure, bool> predicate) => _list.FirstOrDefault(predicate);
|
|
||||||
public Structure Find(Predicate<Structure> predicate) => _list.Find(predicate);
|
|
||||||
public List<Structure> FindAll(Predicate<Structure> predicate) => _list.FindAll(predicate);
|
|
||||||
public IEnumerable<Structure> Where(Func<Structure, bool> predicate) => _list.Where(predicate);
|
|
||||||
public bool Any() => _list.Any();
|
|
||||||
public bool Any(Func<Structure, bool> predicate) => _list.Any(predicate);
|
|
||||||
public void ForEach(Action<Structure> action) => _list.ForEach(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
partial class WallSection : IIgnorable
|
partial class WallSection : IIgnorable
|
||||||
{
|
{
|
||||||
public Rectangle rect;
|
public Rectangle rect;
|
||||||
@@ -106,7 +48,7 @@ namespace Barotrauma
|
|||||||
partial class Structure : MapEntity, IDamageable, IServerSerializable, ISerializableEntity
|
partial class Structure : MapEntity, IDamageable, IServerSerializable, ISerializableEntity
|
||||||
{
|
{
|
||||||
public const int WallSectionSize = 96;
|
public const int WallSectionSize = 96;
|
||||||
public static ThreadSafeStructureList WallList = new ThreadSafeStructureList();
|
public static List<Structure> WallList = new List<Structure>();
|
||||||
|
|
||||||
const float LeakThreshold = 0.1f;
|
const float LeakThreshold = 0.1f;
|
||||||
const float BigGapThreshold = 0.7f;
|
const float BigGapThreshold = 0.7f;
|
||||||
|
|||||||
@@ -12,70 +12,11 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Voronoi2;
|
using Voronoi2;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for Submarine list operations.
|
|
||||||
/// Uses copy-on-write pattern for lock-free reads.
|
|
||||||
/// </summary>
|
|
||||||
internal class ThreadSafeSubmarineList : IEnumerable<Submarine>
|
|
||||||
{
|
|
||||||
private volatile List<Submarine> _list = new List<Submarine>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(Submarine submarine)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Submarine>(_list) { submarine };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(Submarine submarine)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<Submarine>(_list);
|
|
||||||
bool removed = newList.Remove(submarine);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<Submarine>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(Submarine submarine) => _list.Contains(submarine);
|
|
||||||
|
|
||||||
public Submarine this[int index] => _list[index];
|
|
||||||
|
|
||||||
public IEnumerator<Submarine> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// LINQ-friendly methods
|
|
||||||
public List<Submarine> ToList() => new List<Submarine>(_list);
|
|
||||||
public Submarine FirstOrDefault(Func<Submarine, bool> predicate) => _list.FirstOrDefault(predicate);
|
|
||||||
public Submarine Find(Predicate<Submarine> predicate) => _list.Find(predicate);
|
|
||||||
public List<Submarine> FindAll(Predicate<Submarine> predicate) => _list.FindAll(predicate);
|
|
||||||
public IEnumerable<Submarine> Where(Func<Submarine, bool> predicate) => _list.Where(predicate);
|
|
||||||
public bool Any() => _list.Any();
|
|
||||||
public bool Any(Func<Submarine, bool> predicate) => _list.Any(predicate);
|
|
||||||
public float Sum(Func<Submarine, float> selector) => _list.Sum(selector);
|
|
||||||
public IEnumerable<TResult> Select<TResult>(Func<Submarine, TResult> selector) => _list.Select(selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Direction : byte
|
public enum Direction : byte
|
||||||
{
|
{
|
||||||
None = 0, Left = 1, Right = 2
|
None = 0, Left = 1, Right = 2
|
||||||
@@ -131,7 +72,7 @@ namespace Barotrauma
|
|||||||
get { return MainSubs[0]; }
|
get { return MainSubs[0]; }
|
||||||
set { MainSubs[0] = value; }
|
set { MainSubs[0] = value; }
|
||||||
}
|
}
|
||||||
private static readonly ThreadSafeSubmarineList loaded = new ThreadSafeSubmarineList();
|
private static readonly List<Submarine> loaded = new List<Submarine>();
|
||||||
|
|
||||||
private readonly Identifier upgradeEventIdentifier;
|
private readonly Identifier upgradeEventIdentifier;
|
||||||
|
|
||||||
@@ -156,11 +97,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThreadLocal for thread-safe ray casting results
|
private static Vector2 lastPickedPosition;
|
||||||
private static readonly ThreadLocal<Vector2> lastPickedPositionLocal = new ThreadLocal<Vector2>();
|
private static float lastPickedFraction;
|
||||||
private static readonly ThreadLocal<float> lastPickedFractionLocal = new ThreadLocal<float>();
|
private static Fixture lastPickedFixture;
|
||||||
private static readonly ThreadLocal<Fixture> lastPickedFixtureLocal = new ThreadLocal<Fixture>();
|
private static Vector2 lastPickedNormal;
|
||||||
private static readonly ThreadLocal<Vector2> lastPickedNormalLocal = new ThreadLocal<Vector2>();
|
|
||||||
|
|
||||||
private Vector2 prevPosition;
|
private Vector2 prevPosition;
|
||||||
|
|
||||||
@@ -174,22 +114,22 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static Vector2 LastPickedPosition
|
public static Vector2 LastPickedPosition
|
||||||
{
|
{
|
||||||
get { return lastPickedPositionLocal.Value; }
|
get { return lastPickedPosition; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float LastPickedFraction
|
public static float LastPickedFraction
|
||||||
{
|
{
|
||||||
get { return lastPickedFractionLocal.Value; }
|
get { return lastPickedFraction; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Fixture LastPickedFixture
|
public static Fixture LastPickedFixture
|
||||||
{
|
{
|
||||||
get { return lastPickedFixtureLocal.Value; }
|
get { return lastPickedFixture; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vector2 LastPickedNormal
|
public static Vector2 LastPickedNormal
|
||||||
{
|
{
|
||||||
get { return lastPickedNormalLocal.Value; }
|
get { return lastPickedNormal; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Loading
|
public bool Loading
|
||||||
@@ -206,7 +146,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public List<WayPoint> ForcedOutpostModuleWayPoints = new List<WayPoint>();
|
public List<WayPoint> ForcedOutpostModuleWayPoints = new List<WayPoint>();
|
||||||
|
|
||||||
public static ThreadSafeSubmarineList Loaded
|
public static List<Submarine> Loaded
|
||||||
{
|
{
|
||||||
get { return loaded; }
|
get { return loaded; }
|
||||||
}
|
}
|
||||||
@@ -914,10 +854,10 @@ namespace Barotrauma
|
|||||||
}, ref aabb);
|
}, ref aabb);
|
||||||
if (closestFraction <= 0.0f)
|
if (closestFraction <= 0.0f)
|
||||||
{
|
{
|
||||||
lastPickedPositionLocal.Value = rayStart;
|
lastPickedPosition = rayStart;
|
||||||
lastPickedFractionLocal.Value = closestFraction;
|
lastPickedFraction = closestFraction;
|
||||||
lastPickedFixtureLocal.Value = closestFixture;
|
lastPickedFixture = closestFixture;
|
||||||
lastPickedNormalLocal.Value = closestNormal;
|
lastPickedNormal = closestNormal;
|
||||||
return closestBody;
|
return closestBody;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -936,22 +876,16 @@ namespace Barotrauma
|
|||||||
return fraction;
|
return fraction;
|
||||||
}, rayStart, rayEnd, collisionCategory ?? Category.All);
|
}, rayStart, rayEnd, collisionCategory ?? Category.All);
|
||||||
|
|
||||||
lastPickedPositionLocal.Value = rayStart + (rayEnd - rayStart) * closestFraction;
|
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||||
lastPickedFractionLocal.Value = closestFraction;
|
lastPickedFraction = closestFraction;
|
||||||
lastPickedFixtureLocal.Value = closestFixture;
|
lastPickedFixture = closestFixture;
|
||||||
lastPickedNormalLocal.Value = closestNormal;
|
lastPickedNormal = closestNormal;
|
||||||
|
|
||||||
return closestBody;
|
return closestBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThreadLocal for thread-safe body picking
|
private static readonly Dictionary<Body, float> bodyDist = new Dictionary<Body, float>();
|
||||||
private static readonly ThreadLocal<Dictionary<Body, float>> bodyDistLocal =
|
private static readonly List<Body> bodies = new List<Body>();
|
||||||
new ThreadLocal<Dictionary<Body, float>>(() => new Dictionary<Body, float>());
|
|
||||||
private static readonly ThreadLocal<List<Body>> bodiesLocal =
|
|
||||||
new ThreadLocal<List<Body>>(() => new List<Body>());
|
|
||||||
|
|
||||||
private static Dictionary<Body, float> bodyDist => bodyDistLocal.Value;
|
|
||||||
private static List<Body> bodies => bodiesLocal.Value;
|
|
||||||
|
|
||||||
public static float LastPickedBodyDist(Body body)
|
public static float LastPickedBodyDist(Body body)
|
||||||
{
|
{
|
||||||
@@ -985,10 +919,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (fraction < closestFraction)
|
if (fraction < closestFraction)
|
||||||
{
|
{
|
||||||
lastPickedPositionLocal.Value = rayStart + (rayEnd - rayStart) * fraction;
|
lastPickedPosition = rayStart + (rayEnd - rayStart) * fraction;
|
||||||
lastPickedFractionLocal.Value = fraction;
|
lastPickedFraction = fraction;
|
||||||
lastPickedNormalLocal.Value = normal;
|
lastPickedNormal = normal;
|
||||||
lastPickedFixtureLocal.Value = fixture;
|
lastPickedFixture = fixture;
|
||||||
}
|
}
|
||||||
//continue
|
//continue
|
||||||
return -1;
|
return -1;
|
||||||
@@ -1006,10 +940,10 @@ namespace Barotrauma
|
|||||||
if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
|
if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
|
||||||
|
|
||||||
closestFraction = 0.0f;
|
closestFraction = 0.0f;
|
||||||
lastPickedPositionLocal.Value = rayStart;
|
lastPickedPosition = rayStart;
|
||||||
lastPickedFractionLocal.Value = 0.0f;
|
lastPickedFraction = 0.0f;
|
||||||
lastPickedNormalLocal.Value = Vector2.Normalize(rayEnd - rayStart);
|
lastPickedNormal = Vector2.Normalize(rayEnd - rayStart);
|
||||||
lastPickedFixtureLocal.Value = fixture;
|
lastPickedFixture = fixture;
|
||||||
bodies.Add(fixture.Body);
|
bodies.Add(fixture.Body);
|
||||||
bodyDist[fixture.Body] = 0.0f;
|
bodyDist[fixture.Body] = 0.0f;
|
||||||
return false;
|
return false;
|
||||||
@@ -1077,7 +1011,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.01f)
|
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.01f)
|
||||||
{
|
{
|
||||||
lastPickedPositionLocal.Value = rayEnd;
|
lastPickedPosition = rayEnd;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1119,10 +1053,10 @@ namespace Barotrauma
|
|||||||
, rayStart, rayEnd);
|
, rayStart, rayEnd);
|
||||||
|
|
||||||
|
|
||||||
lastPickedPositionLocal.Value = rayStart + (rayEnd - rayStart) * closestFraction;
|
lastPickedPosition = rayStart + (rayEnd - rayStart) * closestFraction;
|
||||||
lastPickedFractionLocal.Value = closestFraction;
|
lastPickedFraction = closestFraction;
|
||||||
lastPickedFixtureLocal.Value = closestFixture;
|
lastPickedFixture = closestFixture;
|
||||||
lastPickedNormalLocal.Value = closestNormal;
|
lastPickedNormal = closestNormal;
|
||||||
return closestBody;
|
return closestBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1143,7 +1077,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Item.UpdateHulls();
|
Item.UpdateHulls();
|
||||||
|
|
||||||
List<Item> bodyItems = Item.ItemList.Where(it => it.Submarine == this && it.body != null).ToList();
|
List<Item> bodyItems = Item.ItemList.FindAll(it => it.Submarine == this && it.body != null);
|
||||||
List<MapEntity> subEntities = MapEntity.MapEntityList.FindAll(me => me.Submarine == this);
|
List<MapEntity> subEntities = MapEntity.MapEntityList.FindAll(me => me.Submarine == this);
|
||||||
|
|
||||||
foreach (MapEntity e in subEntities)
|
foreach (MapEntity e in subEntities)
|
||||||
@@ -1577,9 +1511,9 @@ namespace Barotrauma
|
|||||||
public List<WayPoint> GetWaypoints(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, WayPoint.WayPointList);
|
public List<WayPoint> GetWaypoints(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, WayPoint.WayPointList);
|
||||||
public List<Structure> GetWalls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Structure.WallList);
|
public List<Structure> GetWalls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Structure.WallList);
|
||||||
|
|
||||||
public List<T> GetEntities<T>(bool includingConnectedSubs, IEnumerable<T> list) where T : MapEntity
|
public List<T> GetEntities<T>(bool includingConnectedSubs, List<T> list) where T : MapEntity
|
||||||
{
|
{
|
||||||
return list.Where(e => IsEntityFoundOnThisSub(e, includingConnectedSubs)).ToList();
|
return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<(ItemContainer container, int freeSlots)> GetCargoContainers()
|
public List<(ItemContainer container, int freeSlots)> GetCargoContainers()
|
||||||
@@ -1604,6 +1538,11 @@ namespace Barotrauma
|
|||||||
return containers;
|
return containers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<T> GetEntities<T>(bool includingConnectedSubs, IEnumerable<T> list) where T : MapEntity
|
||||||
|
{
|
||||||
|
return list.Where(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam = false, bool allowDifferentType = false)
|
public bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam = false, bool allowDifferentType = false)
|
||||||
{
|
{
|
||||||
if (entity == null) { return false; }
|
if (entity == null) { return false; }
|
||||||
@@ -1727,8 +1666,9 @@ namespace Barotrauma
|
|||||||
HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y;
|
HiddenSubPosition += Vector2.UnitY * GameMain.GameSession.LevelData.Size.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Submarine sub in loaded)
|
for (int i = 0; i < loaded.Count; i++)
|
||||||
{
|
{
|
||||||
|
Submarine sub = loaded[i];
|
||||||
HiddenSubPosition =
|
HiddenSubPosition =
|
||||||
new Vector2(
|
new Vector2(
|
||||||
//1st sub on the left side, 2nd on the right, etc
|
//1st sub on the left side, 2nd on the right, etc
|
||||||
@@ -1860,9 +1800,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
entityGrid = Hull.GenerateEntityGrid(this);
|
entityGrid = Hull.GenerateEntityGrid(this);
|
||||||
|
|
||||||
foreach (MapEntity me in MapEntity.MapEntityList.Where(e => e.Submarine == this))
|
for (int i = 0; i < MapEntity.MapEntityList.Count; i++)
|
||||||
{
|
{
|
||||||
me.Move(HiddenSubPosition, ignoreContacts: true);
|
if (MapEntity.MapEntityList[i].Submarine != this) { continue; }
|
||||||
|
MapEntity.MapEntityList[i].Move(HiddenSubPosition, ignoreContacts: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Loading = false;
|
Loading = false;
|
||||||
@@ -2207,7 +2148,7 @@ namespace Barotrauma
|
|||||||
DebugConsole.ThrowError("Error while removing \"" + item.Name + "\"!", e);
|
DebugConsole.ThrowError("Error while removing \"" + item.Name + "\"!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item.ClearAllItemCollections();
|
Item.ItemList.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ragdoll.RemoveAll();
|
Ragdoll.RemoveAll();
|
||||||
@@ -2216,7 +2157,7 @@ namespace Barotrauma
|
|||||||
GameMain.World = null;
|
GameMain.World = null;
|
||||||
|
|
||||||
Powered.Grids.Clear();
|
Powered.Grids.Clear();
|
||||||
Powered.ClearChangedConnections();
|
Powered.ChangedConnections.Clear();
|
||||||
|
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
|
|
||||||
@@ -2256,7 +2197,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
ConnectedDockingPorts?.Clear();
|
ConnectedDockingPorts?.Clear();
|
||||||
|
|
||||||
Powered.ClearChangedConnections();
|
Powered.ChangedConnections.Clear();
|
||||||
Powered.Grids.Clear();
|
Powered.Grids.Clear();
|
||||||
|
|
||||||
loaded.Remove(this);
|
loaded.Remove(this);
|
||||||
|
|||||||
@@ -5,75 +5,17 @@ using Microsoft.Xna.Framework;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for WayPoint list operations.
|
|
||||||
/// Uses copy-on-write pattern for lock-free reads.
|
|
||||||
/// </summary>
|
|
||||||
internal class ThreadSafeWayPointList : IEnumerable<WayPoint>
|
|
||||||
{
|
|
||||||
private volatile List<WayPoint> _list = new List<WayPoint>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(WayPoint waypoint)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<WayPoint>(_list) { waypoint };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(WayPoint waypoint)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<WayPoint>(_list);
|
|
||||||
bool removed = newList.Remove(waypoint);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<WayPoint>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(WayPoint waypoint) => _list.Contains(waypoint);
|
|
||||||
|
|
||||||
public WayPoint this[int index] => _list[index];
|
|
||||||
|
|
||||||
public IEnumerator<WayPoint> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// LINQ-friendly methods
|
|
||||||
public List<WayPoint> ToList() => new List<WayPoint>(_list);
|
|
||||||
public WayPoint FirstOrDefault(Func<WayPoint, bool> predicate) => _list.FirstOrDefault(predicate);
|
|
||||||
public WayPoint Find(Predicate<WayPoint> predicate) => _list.Find(predicate);
|
|
||||||
public List<WayPoint> FindAll(Predicate<WayPoint> predicate) => _list.FindAll(predicate);
|
|
||||||
public IEnumerable<WayPoint> Where(Func<WayPoint, bool> predicate) => _list.Where(predicate);
|
|
||||||
public bool Any() => _list.Any();
|
|
||||||
public bool Any(Func<WayPoint, bool> predicate) => _list.Any(predicate);
|
|
||||||
public bool Exists(Predicate<WayPoint> predicate) => _list.Exists(predicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 4, Corpse = 8, Submarine = 16, ExitPoint = 32, Disabled = 64 };
|
public enum SpawnType { Path = 0, Human = 1, Enemy = 2, Cargo = 4, Corpse = 8, Submarine = 16, ExitPoint = 32, Disabled = 64 };
|
||||||
|
|
||||||
partial class WayPoint : MapEntity
|
partial class WayPoint : MapEntity
|
||||||
{
|
{
|
||||||
public static ThreadSafeWayPointList WayPointList = new ThreadSafeWayPointList();
|
public static List<WayPoint> WayPointList = new List<WayPoint>();
|
||||||
|
|
||||||
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
|
public static bool ShowWayPoints = true, ShowSpawnPoints = true;
|
||||||
|
|
||||||
@@ -990,7 +932,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false, string spawnPointTag = null, bool ignoreSubmarine = false)
|
public static WayPoint GetRandom(SpawnType spawnType = SpawnType.Human, JobPrefab assignedJob = null, Submarine sub = null, bool useSyncedRand = false, string spawnPointTag = null, bool ignoreSubmarine = false)
|
||||||
{
|
{
|
||||||
return WayPointList.ToList().GetRandom(wp =>
|
return WayPointList.GetRandom(wp =>
|
||||||
(ignoreSubmarine || wp.Submarine == sub) &&
|
(ignoreSubmarine || wp.Submarine == sub) &&
|
||||||
//checking for the disabled flag is not strictly necessary because we check for equality of the spawn type,
|
//checking for the disabled flag is not strictly necessary because we check for equality of the spawn type,
|
||||||
//but lets do that anyway in case we change the handling of the spawn type at some point
|
//but lets do that anyway in case we change the handling of the spawn type at some point
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace Barotrauma.Networking
|
|||||||
DebugConsole.Log($"Changed client {Name}'s team to {teamID}.");
|
DebugConsole.Log($"Changed client {Name}'s team to {teamID}.");
|
||||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||||
{
|
{
|
||||||
GameMain.NetworkMember.IncrementLastClientListUpdateID();
|
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||||
}
|
}
|
||||||
teamID = value;
|
teamID = value;
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||||
{
|
{
|
||||||
GameMain.NetworkMember.IncrementLastClientListUpdateID();
|
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
CharacterID = value.ID;
|
CharacterID = value.ID;
|
||||||
@@ -154,7 +154,7 @@ namespace Barotrauma.Networking
|
|||||||
#endif
|
#endif
|
||||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||||
{
|
{
|
||||||
GameMain.NetworkMember.IncrementLastClientListUpdateID();
|
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||||
{
|
{
|
||||||
GameMain.NetworkMember.IncrementLastClientListUpdateID();
|
GameMain.NetworkMember.LastClientListUpdateID++;
|
||||||
}
|
}
|
||||||
inGame = value;
|
inGame = value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ namespace Barotrauma
|
|||||||
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||||
while (spawnOrRemoveQueue.Count > 0)
|
while (spawnOrRemoveQueue.Count > 0)
|
||||||
{
|
{
|
||||||
if (!spawnOrRemoveQueue.TryDequeue(out var spawnOrRemove)) { break; }
|
var spawnOrRemove = spawnOrRemoveQueue.Dequeue();
|
||||||
if (spawnOrRemove.TryGet(out Entity entityToRemove))
|
if (spawnOrRemove.TryGet(out Entity entityToRemove))
|
||||||
{
|
{
|
||||||
if (entityToRemove is Item item)
|
if (entityToRemove is Item item)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
@@ -147,10 +146,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<Type, ImmutableArray<CachedReflectedVariable>> CachedVariables = new ConcurrentDictionary<Type, ImmutableArray<CachedReflectedVariable>>();
|
private static readonly Dictionary<Type, ImmutableArray<CachedReflectedVariable>> CachedVariables = new Dictionary<Type, ImmutableArray<CachedReflectedVariable>>();
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<Type, IReadWriteBehavior> TypeBehaviors
|
private static readonly Dictionary<Type, IReadWriteBehavior> TypeBehaviors
|
||||||
= new ConcurrentDictionary<Type, IReadWriteBehavior>(new Dictionary<Type, IReadWriteBehavior>
|
= new Dictionary<Type, IReadWriteBehavior>
|
||||||
{
|
{
|
||||||
{ typeof(Boolean), new ReadWriteBehavior<Boolean>(ReadBoolean, WriteBoolean) },
|
{ typeof(Boolean), new ReadWriteBehavior<Boolean>(ReadBoolean, WriteBoolean) },
|
||||||
{ typeof(Byte), new ReadWriteBehavior<Byte>(ReadByte, WriteByte) },
|
{ typeof(Byte), new ReadWriteBehavior<Byte>(ReadByte, WriteByte) },
|
||||||
@@ -169,7 +168,7 @@ namespace Barotrauma
|
|||||||
{ typeof(Vector2), new ReadWriteBehavior<Vector2>(ReadVector2, WriteVector2) },
|
{ typeof(Vector2), new ReadWriteBehavior<Vector2>(ReadVector2, WriteVector2) },
|
||||||
{ typeof(SerializableDateTime), new ReadWriteBehavior<SerializableDateTime>(ReadSerializableDateTime, WriteSerializableDateTime) },
|
{ typeof(SerializableDateTime), new ReadWriteBehavior<SerializableDateTime>(ReadSerializableDateTime, WriteSerializableDateTime) },
|
||||||
{ typeof(NetLimitedString), new ReadWriteBehavior<NetLimitedString>(ReadNetLString, WriteNetLString) }
|
{ typeof(NetLimitedString), new ReadWriteBehavior<NetLimitedString>(ReadNetLString, WriteNetLString) }
|
||||||
});
|
};
|
||||||
|
|
||||||
private static readonly ImmutableDictionary<Predicate<Type>, Func<Type, IReadWriteBehavior>> BehaviorFactories = new Dictionary<Predicate<Type>, Func<Type, IReadWriteBehavior>>
|
private static readonly ImmutableDictionary<Predicate<Type>, Func<Type, IReadWriteBehavior>> BehaviorFactories = new Dictionary<Predicate<Type>, Func<Type, IReadWriteBehavior>>
|
||||||
{
|
{
|
||||||
@@ -585,11 +584,7 @@ namespace Barotrauma
|
|||||||
if (!predicate(type)) { continue; }
|
if (!predicate(type)) { continue; }
|
||||||
|
|
||||||
behavior = factory(type);
|
behavior = factory(type);
|
||||||
// Use TryAdd for thread-safety; if another thread already added, use that value
|
TypeBehaviors.Add(type, behavior);
|
||||||
if (!TypeBehaviors.TryAdd(type, behavior))
|
|
||||||
{
|
|
||||||
behavior = TypeBehaviors[type];
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,11 +594,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static ImmutableArray<CachedReflectedVariable> GetPropertiesAndFields(Type type)
|
public static ImmutableArray<CachedReflectedVariable> GetPropertiesAndFields(Type type)
|
||||||
{
|
{
|
||||||
return CachedVariables.GetOrAdd(type, static t => CreateCachedVariables(t));
|
if (CachedVariables.TryGetValue(type, out var cached)) { return cached; }
|
||||||
}
|
|
||||||
|
|
||||||
private static ImmutableArray<CachedReflectedVariable> CreateCachedVariables(Type type)
|
|
||||||
{
|
|
||||||
List<CachedReflectedVariable> variables = new List<CachedReflectedVariable>();
|
List<CachedReflectedVariable> variables = new List<CachedReflectedVariable>();
|
||||||
|
|
||||||
IEnumerable<PropertyInfo> propertyInfos = type.GetProperties().Where(HasAttribute).Where(NotStatic);
|
IEnumerable<PropertyInfo> propertyInfos = type.GetProperties().Where(HasAttribute).Where(NotStatic);
|
||||||
@@ -641,6 +633,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImmutableArray<CachedReflectedVariable> array = variables.All(v => v.HasOwnAttribute) ? variables.OrderBy(v => v.Attribute.OrderKey).ToImmutableArray() : variables.ToImmutableArray();
|
ImmutableArray<CachedReflectedVariable> array = variables.All(v => v.HasOwnAttribute) ? variables.OrderBy(v => v.Attribute.OrderKey).ToImmutableArray() : variables.ToImmutableArray();
|
||||||
|
CachedVariables.Add(type, array);
|
||||||
return array;
|
return array;
|
||||||
|
|
||||||
bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute<NetworkSerialize>() ?? type.GetCustomAttribute<NetworkSerialize>()) != null;
|
bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute<NetworkSerialize>() ?? type.GetCustomAttribute<NetworkSerialize>()) != null;
|
||||||
@@ -881,4 +874,4 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Barotrauma.Networking
|
namespace Barotrauma.Networking
|
||||||
{
|
{
|
||||||
@@ -187,19 +186,10 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
protected const int MaxSubNameLengthInErrorMessages = 16;
|
protected const int MaxSubNameLengthInErrorMessages = 16;
|
||||||
|
|
||||||
private int lastClientListUpdateID;
|
|
||||||
public UInt16 LastClientListUpdateID
|
public UInt16 LastClientListUpdateID
|
||||||
{
|
{
|
||||||
get => (UInt16)Interlocked.CompareExchange(ref lastClientListUpdateID, 0, 0);
|
get;
|
||||||
set => Interlocked.Exchange(ref lastClientListUpdateID, value);
|
set;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe increment of LastClientListUpdateID
|
|
||||||
/// </summary>
|
|
||||||
public void IncrementLastClientListUpdateID()
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref lastClientListUpdateID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract bool IsServer { get; }
|
public abstract bool IsServer { get; }
|
||||||
|
|||||||
@@ -4,69 +4,12 @@ using FarseerPhysics.Dynamics;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using LimbParams = Barotrauma.RagdollParams.LimbParams;
|
using LimbParams = Barotrauma.RagdollParams.LimbParams;
|
||||||
using ColliderParams = Barotrauma.RagdollParams.ColliderParams;
|
using ColliderParams = Barotrauma.RagdollParams.ColliderParams;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe wrapper for PhysicsBody list operations.
|
|
||||||
/// Uses copy-on-write pattern for lock-free reads.
|
|
||||||
/// </summary>
|
|
||||||
internal class ThreadSafePhysicsBodyList : IEnumerable<PhysicsBody>
|
|
||||||
{
|
|
||||||
private volatile List<PhysicsBody> _list = new List<PhysicsBody>();
|
|
||||||
private readonly object _writeLock = new object();
|
|
||||||
|
|
||||||
public int Count => _list.Count;
|
|
||||||
|
|
||||||
public void Add(PhysicsBody body)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<PhysicsBody>(_list) { body };
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(PhysicsBody body)
|
|
||||||
{
|
|
||||||
lock (_writeLock)
|
|
||||||
{
|
|
||||||
var newList = new List<PhysicsBody>(_list);
|
|
||||||
bool removed = newList.Remove(body);
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, newList);
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _list, new List<PhysicsBody>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(PhysicsBody body) => _list.Contains(body);
|
|
||||||
|
|
||||||
public PhysicsBody this[int index] => _list[index];
|
|
||||||
|
|
||||||
public IEnumerator<PhysicsBody> GetEnumerator() => _list.GetEnumerator();
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// LINQ-friendly methods
|
|
||||||
public List<PhysicsBody> ToList() => new List<PhysicsBody>(_list);
|
|
||||||
public PhysicsBody FirstOrDefault(Func<PhysicsBody, bool> predicate) => _list.FirstOrDefault(predicate);
|
|
||||||
public PhysicsBody Find(Predicate<PhysicsBody> predicate) => _list.Find(predicate);
|
|
||||||
public IEnumerable<PhysicsBody> Where(Func<PhysicsBody, bool> predicate) => _list.Where(predicate);
|
|
||||||
public bool Any() => _list.Any();
|
|
||||||
public bool Any(Func<PhysicsBody, bool> predicate) => _list.Any(predicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PosInfo
|
class PosInfo
|
||||||
{
|
{
|
||||||
public Vector2 Position
|
public Vector2 Position
|
||||||
@@ -149,8 +92,8 @@ namespace Barotrauma
|
|||||||
public const float MinDensity = 0.01f;
|
public const float MinDensity = 0.01f;
|
||||||
public const float DefaultAngularDamping = 5.0f;
|
public const float DefaultAngularDamping = 5.0f;
|
||||||
|
|
||||||
private static readonly ThreadSafePhysicsBodyList list = new ThreadSafePhysicsBodyList();
|
private static readonly List<PhysicsBody> list = new List<PhysicsBody>();
|
||||||
public static ThreadSafePhysicsBodyList List
|
public static List<PhysicsBody> List
|
||||||
{
|
{
|
||||||
get { return list; }
|
get { return list; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe queue for deferring physics operations to the main thread.
|
|
||||||
/// This is necessary because Farseer Physics' DynamicTree is not thread-safe,
|
|
||||||
/// and physics operations cannot be safely performed during parallel updates.
|
|
||||||
///
|
|
||||||
/// Supported operations include:
|
|
||||||
/// - Physics body creation
|
|
||||||
/// - Physics body transform updates (SetTransform, SetTransformIgnoreContacts)
|
|
||||||
/// - Any other operation that modifies the Farseer physics world
|
|
||||||
/// </summary>
|
|
||||||
/// <start>
|
|
||||||
/// ├─> PhysicsBodyQueue.IsInParallelContext = true (ThreadStatic)
|
|
||||||
/// ├─> Item.Update()
|
|
||||||
/// │ └─> StatusEffect.Apply()
|
|
||||||
/// │ └─> Character.Kill()
|
|
||||||
/// │ └─> Item.Drop()
|
|
||||||
/// │ └─> Check if IsInParallelContext == true
|
|
||||||
/// │ └─> PhysicsBodyQueue.Enqueue(Physics operation)
|
|
||||||
/// ├──> PhysicsBodyQueue.IsInParallelContext = false
|
|
||||||
/// └──> PhysicsBodyQueue.ProcessPendingOperations() ← Main thread executes
|
|
||||||
/// └─> body.SetTransformIgnoreContacts()
|
|
||||||
static class PhysicsBodyQueue
|
|
||||||
{
|
|
||||||
private static readonly object _lock = new object();
|
|
||||||
private static readonly Queue<Action> _pendingOperations = new Queue<Action>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Thread-local flag indicating whether the current thread is in a parallel physics update context.
|
|
||||||
/// When true, physics operations should be deferred using this queue instead of executing immediately.
|
|
||||||
/// </summary>
|
|
||||||
[ThreadStatic]
|
|
||||||
private static bool _isInParallelContext;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets whether the current thread is in a parallel update context.
|
|
||||||
/// When true, physics operations should be queued instead of executed immediately.
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsInParallelContext
|
|
||||||
{
|
|
||||||
get => _isInParallelContext;
|
|
||||||
set => _isInParallelContext = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enqueues a physics operation to be executed on the main thread.
|
|
||||||
/// This method is thread-safe and can be called from parallel update loops.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="operation">The physics operation to defer</param>
|
|
||||||
public static void Enqueue(Action operation)
|
|
||||||
{
|
|
||||||
if (operation == null) { return; }
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_pendingOperations.Enqueue(operation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enqueues a physics body creation action to be executed on the main thread.
|
|
||||||
/// This method is thread-safe and can be called from parallel update loops.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="createAction">The action that creates the physics body</param>
|
|
||||||
public static void EnqueueCreation(Action createAction)
|
|
||||||
{
|
|
||||||
Enqueue(createAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Executes a physics operation, either immediately or deferred depending on context.
|
|
||||||
/// If called from a parallel context, the operation will be queued for later execution.
|
|
||||||
/// If called from the main thread (outside parallel loops), the operation executes immediately.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="operation">The physics operation to execute</param>
|
|
||||||
public static void ExecuteOrDefer(Action operation)
|
|
||||||
{
|
|
||||||
if (operation == null) { return; }
|
|
||||||
|
|
||||||
if (_isInParallelContext)
|
|
||||||
{
|
|
||||||
Enqueue(operation);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
operation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the number of pending physics operations.
|
|
||||||
/// </summary>
|
|
||||||
public static int PendingCount
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
return _pendingOperations.Count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processes all pending physics operations.
|
|
||||||
/// Must be called on the main thread, outside of any parallel loops.
|
|
||||||
/// </summary>
|
|
||||||
public static void ProcessPendingOperations()
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
Action action;
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_pendingOperations.Count == 0) { break; }
|
|
||||||
action = _pendingOperations.Dequeue();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
action?.Invoke();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DebugConsole.ThrowError($"Error processing deferred physics operation: {e.Message}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Legacy method for backwards compatibility.
|
|
||||||
/// Calls ProcessPendingOperations().
|
|
||||||
/// </summary>
|
|
||||||
public static void ProcessPendingCreations()
|
|
||||||
{
|
|
||||||
ProcessPendingOperations();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears all pending physics operations.
|
|
||||||
/// Should be called when ending a round or cleaning up.
|
|
||||||
/// </summary>
|
|
||||||
public static void Clear()
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_pendingOperations.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -178,23 +178,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Parallel.Invoke(parallelOptions,
|
Parallel.Invoke(parallelOptions,
|
||||||
() => GameMain.ParticleManager.Update((float)deltaTime),
|
() => GameMain.ParticleManager.Update((float)deltaTime),
|
||||||
() =>
|
() => { if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, cam); }
|
||||||
{
|
|
||||||
PhysicsBodyQueue.IsInParallelContext = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, cam);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PhysicsBodyQueue.IsInParallelContext = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Process any physics operations queued during Level update
|
|
||||||
PhysicsBodyQueue.ProcessPendingOperations();
|
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
GameMain.PerformanceCounter.AddElapsedTicks("Update:Particles+Level", sw.ElapsedTicks);
|
GameMain.PerformanceCounter.AddElapsedTicks("Update:Particles+Level", sw.ElapsedTicks);
|
||||||
|
|
||||||
@@ -260,33 +246,12 @@ namespace Barotrauma
|
|||||||
|
|
||||||
#elif SERVER
|
#elif SERVER
|
||||||
Parallel.Invoke(parallelOptions,
|
Parallel.Invoke(parallelOptions,
|
||||||
() =>
|
() => { if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance); },
|
||||||
{
|
() => Character.UpdateAll((float)deltaTime, Camera.Instance)
|
||||||
PhysicsBodyQueue.IsInParallelContext = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Level.Loaded != null) Level.Loaded.Update((float)deltaTime, Camera.Instance);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PhysicsBodyQueue.IsInParallelContext = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() =>
|
|
||||||
{
|
|
||||||
PhysicsBodyQueue.IsInParallelContext = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Character.UpdateAll((float)deltaTime, Camera.Instance);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
PhysicsBodyQueue.IsInParallelContext = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
PhysicsBodyQueue.ProcessPendingOperations();
|
//StatusEffect.UpdateAll is not thread-safe and must be executed on the main thread
|
||||||
|
StatusEffect.UpdateAll((float)deltaTime);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var submarines = Submarine.Loaded.ToList();
|
var submarines = Submarine.Loaded.ToList();
|
||||||
@@ -309,9 +274,7 @@ namespace Barotrauma
|
|||||||
#elif SERVER
|
#elif SERVER
|
||||||
|
|
||||||
MapEntity.UpdateAll((float)deltaTime, Camera.Instance, parallelOptions);
|
MapEntity.UpdateAll((float)deltaTime, Camera.Instance, parallelOptions);
|
||||||
|
|
||||||
StatusEffect.UpdateAll((float)deltaTime);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
|
|||||||
@@ -1,56 +1,26 @@
|
|||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class DelayedListElement
|
class DelayedListElement
|
||||||
{
|
{
|
||||||
public readonly long Id;
|
|
||||||
public readonly DelayedEffect Parent;
|
public readonly DelayedEffect Parent;
|
||||||
public readonly Entity Entity;
|
public readonly Entity Entity;
|
||||||
|
public Vector2? WorldPosition;
|
||||||
private Vector2? _worldPosition;
|
|
||||||
private readonly object _worldPositionLock = new object();
|
|
||||||
public Vector2? WorldPosition
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (_worldPositionLock)
|
|
||||||
{
|
|
||||||
return _worldPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
lock (_worldPositionLock)
|
|
||||||
{
|
|
||||||
_worldPosition = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should the delayed effect attempt to determine the position of the effect based on the targets, or just use the position that was passed to the constructor.
|
/// Should the delayed effect attempt to determine the position of the effect based on the targets, or just use the position that was passed to the constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool GetPositionBasedOnTargets;
|
public bool GetPositionBasedOnTargets;
|
||||||
public readonly Vector2? StartPosition;
|
public readonly Vector2? StartPosition;
|
||||||
public readonly List<ISerializableEntity> Targets;
|
public readonly List<ISerializableEntity> Targets;
|
||||||
|
public float Delay;
|
||||||
private volatile float _delay;
|
|
||||||
public float Delay
|
|
||||||
{
|
|
||||||
get => _delay;
|
|
||||||
set => _delay = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DelayedListElement(DelayedEffect parentEffect, Entity parentEntity, IEnumerable<ISerializableEntity> targets, float delay, Vector2? worldPosition, Vector2? startPosition)
|
public DelayedListElement(DelayedEffect parentEffect, Entity parentEntity, IEnumerable<ISerializableEntity> targets, float delay, Vector2? worldPosition, Vector2? startPosition)
|
||||||
{
|
{
|
||||||
Id = Interlocked.Increment(ref DelayedEffect._delayElementIdCounter);
|
|
||||||
Parent = parentEffect;
|
Parent = parentEffect;
|
||||||
Entity = parentEntity;
|
Entity = parentEntity;
|
||||||
Targets = new List<ISerializableEntity>(targets);
|
Targets = new List<ISerializableEntity>(targets);
|
||||||
@@ -59,19 +29,9 @@ namespace Barotrauma
|
|||||||
StartPosition = startPosition;
|
StartPosition = startPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DelayedEffect : StatusEffect
|
class DelayedEffect : StatusEffect
|
||||||
{
|
{
|
||||||
// Thread-safe counter for generating unique IDs for DelayedListElement
|
public static readonly List<DelayedListElement> DelayList = new List<DelayedListElement>();
|
||||||
internal static long _delayElementIdCounter;
|
|
||||||
|
|
||||||
// Thread-safe dictionary for delayed effects
|
|
||||||
public static readonly ConcurrentDictionary<long, DelayedListElement> DelayListDict = new ConcurrentDictionary<long, DelayedListElement>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a thread-safe enumerable view of the delay list for iteration.
|
|
||||||
/// </summary>
|
|
||||||
public static IEnumerable<DelayedListElement> DelayList => DelayListDict.Values;
|
|
||||||
|
|
||||||
private enum DelayTypes
|
private enum DelayTypes
|
||||||
{
|
{
|
||||||
@@ -102,10 +62,9 @@ namespace Barotrauma
|
|||||||
if (this.type != type || !HasRequiredItems(entity)) { return; }
|
if (this.type != type || !HasRequiredItems(entity)) { return; }
|
||||||
if (!Stackable)
|
if (!Stackable)
|
||||||
{
|
{
|
||||||
// Thread-safe iteration over ConcurrentDictionary
|
foreach (var existingEffect in DelayList)
|
||||||
foreach (var kvp in DelayListDict)
|
|
||||||
{
|
{
|
||||||
if (kvp.Value.Parent == this && kvp.Value.Targets.FirstOrDefault() == target)
|
if (existingEffect.Parent == this && existingEffect.Targets.FirstOrDefault() == target)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -113,19 +72,18 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (!IsValidTarget(target)) { return; }
|
if (!IsValidTarget(target)) { return; }
|
||||||
|
|
||||||
var targets = CurrentTargets;
|
currentTargets.Clear();
|
||||||
targets.Clear();
|
currentTargets.Add(target);
|
||||||
targets.Add(target);
|
if (!HasRequiredConditions(currentTargets)) { return; }
|
||||||
if (!HasRequiredConditions(targets)) { return; }
|
|
||||||
|
|
||||||
switch (delayType)
|
switch (delayType)
|
||||||
{
|
{
|
||||||
case DelayTypes.Timer:
|
case DelayTypes.Timer:
|
||||||
var newDelayListElement = new DelayedListElement(this, entity, targets, delay, worldPosition ?? GetPosition(entity, targets, worldPosition), startPosition: null)
|
var newDelayListElement = new DelayedListElement(this, entity, currentTargets, delay, worldPosition ?? GetPosition(entity, currentTargets, worldPosition), startPosition: null)
|
||||||
{
|
{
|
||||||
GetPositionBasedOnTargets = worldPosition == null
|
GetPositionBasedOnTargets = worldPosition == null
|
||||||
};
|
};
|
||||||
DelayListDict.TryAdd(newDelayListElement.Id, newDelayListElement);
|
DelayList.Add(newDelayListElement);
|
||||||
break;
|
break;
|
||||||
case DelayTypes.ReachCursor:
|
case DelayTypes.ReachCursor:
|
||||||
Projectile projectile = (entity as Item)?.GetComponent<Projectile>();
|
Projectile projectile = (entity as Item)?.GetComponent<Projectile>();
|
||||||
@@ -147,8 +105,7 @@ namespace Barotrauma
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var reachCursorElement = new DelayedListElement(this, entity, targets, Vector2.Distance(entity.WorldPosition, projectile.User.CursorWorldPosition), worldPosition, entity.WorldPosition);
|
DelayList.Add(new DelayedListElement(this, entity, currentTargets, Vector2.Distance(entity.WorldPosition, projectile.User.CursorWorldPosition), worldPosition, entity.WorldPosition));
|
||||||
DelayListDict.TryAdd(reachCursorElement.Id, reachCursorElement);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,28 +119,25 @@ namespace Barotrauma
|
|||||||
if (delayType == DelayTypes.ReachCursor && Character.Controlled == null) { return; }
|
if (delayType == DelayTypes.ReachCursor && Character.Controlled == null) { return; }
|
||||||
if (!Stackable)
|
if (!Stackable)
|
||||||
{
|
{
|
||||||
// Thread-safe iteration over ConcurrentDictionary
|
foreach (var existingEffect in DelayList)
|
||||||
foreach (var kvp in DelayListDict)
|
|
||||||
{
|
{
|
||||||
if (kvp.Value.Parent == this && kvp.Value.Targets.SequenceEqual(targets)) { return; }
|
if (existingEffect.Parent == this && existingEffect.Targets.SequenceEqual(targets)) { return; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var localTargets = CurrentTargets;
|
currentTargets.Clear();
|
||||||
localTargets.Clear();
|
|
||||||
foreach (ISerializableEntity target in targets)
|
foreach (ISerializableEntity target in targets)
|
||||||
{
|
{
|
||||||
if (!IsValidTarget(target)) { continue; }
|
if (!IsValidTarget(target)) { continue; }
|
||||||
localTargets.Add(target);
|
currentTargets.Add(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HasRequiredConditions(localTargets)) { return; }
|
if (!HasRequiredConditions(currentTargets)) { return; }
|
||||||
|
|
||||||
switch (delayType)
|
switch (delayType)
|
||||||
{
|
{
|
||||||
case DelayTypes.Timer:
|
case DelayTypes.Timer:
|
||||||
var timerElement = new DelayedListElement(this, entity, localTargets, delay, worldPosition, null);
|
DelayList.Add(new DelayedListElement(this, entity, currentTargets, delay, worldPosition, null));
|
||||||
DelayListDict.TryAdd(timerElement.Id, timerElement);
|
|
||||||
break;
|
break;
|
||||||
case DelayTypes.ReachCursor:
|
case DelayTypes.ReachCursor:
|
||||||
Projectile projectile = (entity as Item)?.GetComponent<Projectile>();
|
Projectile projectile = (entity as Item)?.GetComponent<Projectile>();
|
||||||
@@ -207,21 +161,19 @@ namespace Barotrauma
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var reachCursorElement = new DelayedListElement(this, entity, localTargets, Vector2.Distance(entity.WorldPosition, user.CursorWorldPosition), worldPosition, entity.WorldPosition);
|
DelayList.Add(new DelayedListElement(this, entity, currentTargets, Vector2.Distance(entity.WorldPosition, user.CursorWorldPosition), worldPosition, entity.WorldPosition));
|
||||||
DelayListDict.TryAdd(reachCursorElement.Id, reachCursorElement);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Update(float deltaTime)
|
public static void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
// Thread-safe iteration over ConcurrentDictionary
|
for (int i = DelayList.Count - 1; i >= 0; i--)
|
||||||
foreach (var kvp in DelayListDict)
|
|
||||||
{
|
{
|
||||||
DelayedListElement element = kvp.Value;
|
DelayedListElement element = DelayList[i];
|
||||||
if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets))
|
if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets))
|
||||||
{
|
{
|
||||||
DelayListDict.TryRemove(element.Id, out _);
|
DelayList.Remove(element);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +187,7 @@ namespace Barotrauma
|
|||||||
//keep refreshing the position until the effect runs (so e.g. a delayed effect runs at the last known position of a monster before it despawned)
|
//keep refreshing the position until the effect runs (so e.g. a delayed effect runs at the last known position of a monster before it despawned)
|
||||||
if (element.GetPositionBasedOnTargets && element.Entity is { Removed: false })
|
if (element.GetPositionBasedOnTargets && element.Entity is { Removed: false })
|
||||||
{
|
{
|
||||||
element.WorldPosition = element.Parent.GetPosition(element.Entity, element.Parent.CurrentTargets);
|
element.WorldPosition = element.Parent.GetPosition(element.Entity, element.Parent.currentTargets);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -246,8 +198,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
element.Parent.Apply(deltaTime, element.Entity, element.Targets, element.WorldPosition);
|
element.Parent.Apply(deltaTime, element.Entity, element.Targets, element.WorldPosition);
|
||||||
DelayListDict.TryRemove(element.Id, out _);
|
DelayList.Remove(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,18 +7,15 @@ using FarseerPhysics.Dynamics;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Steamworks;
|
using Steamworks;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class DurationListElement
|
class DurationListElement
|
||||||
{
|
{
|
||||||
public readonly long Id;
|
|
||||||
public readonly StatusEffect Parent;
|
public readonly StatusEffect Parent;
|
||||||
public readonly Entity Entity;
|
public readonly Entity Entity;
|
||||||
public float Duration
|
public float Duration
|
||||||
@@ -27,24 +24,12 @@ namespace Barotrauma
|
|||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
public readonly List<ISerializableEntity> Targets;
|
public readonly List<ISerializableEntity> Targets;
|
||||||
|
public Character User { get; private set; }
|
||||||
private volatile Character _user;
|
|
||||||
public Character User
|
|
||||||
{
|
|
||||||
get => _user;
|
|
||||||
private set => _user = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private volatile float _timer;
|
public float Timer;
|
||||||
public float Timer
|
|
||||||
{
|
|
||||||
get => _timer;
|
|
||||||
set => _timer = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DurationListElement(StatusEffect parentEffect, Entity parentEntity, IEnumerable<ISerializableEntity> targets, float duration, Character user)
|
public DurationListElement(StatusEffect parentEffect, Entity parentEntity, IEnumerable<ISerializableEntity> targets, float duration, Character user)
|
||||||
{
|
{
|
||||||
Id = Interlocked.Increment(ref StatusEffect._durationElementIdCounter);
|
|
||||||
Parent = parentEffect;
|
Parent = parentEffect;
|
||||||
Entity = parentEntity;
|
Entity = parentEntity;
|
||||||
Targets = new List<ISerializableEntity>(targets);
|
Targets = new List<ISerializableEntity>(targets);
|
||||||
@@ -54,9 +39,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void Reset(float duration, Character newUser)
|
public void Reset(float duration, Character newUser)
|
||||||
{
|
{
|
||||||
Duration = duration;
|
Timer = Duration = duration;
|
||||||
Volatile.Write(ref _timer, duration);
|
User = newUser;
|
||||||
_user = newUser;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,23 +601,14 @@ namespace Barotrauma
|
|||||||
private readonly float lifeTime;
|
private readonly float lifeTime;
|
||||||
private float lifeTimer;
|
private float lifeTimer;
|
||||||
|
|
||||||
private ConcurrentDictionary<Entity, float> intervalTimers;
|
private Dictionary<Entity, float> intervalTimers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Makes the effect only execute once. After it has executed, it'll never execute again (during the same round).
|
/// Makes the effect only execute once. After it has executed, it'll never execute again (during the same round).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly bool oneShot;
|
private readonly bool oneShot;
|
||||||
|
|
||||||
// Thread-safe counter for generating unique IDs for DurationListElement
|
public static readonly List<DurationListElement> DurationList = new List<DurationListElement>();
|
||||||
internal static long _durationElementIdCounter;
|
|
||||||
|
|
||||||
// Thread-safe dictionary for duration effects
|
|
||||||
public static readonly ConcurrentDictionary<long, DurationListElement> DurationListDict = new ConcurrentDictionary<long, DurationListElement>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a thread-safe enumerable view of the duration list for iteration.
|
|
||||||
/// </summary>
|
|
||||||
public static IEnumerable<DurationListElement> DurationList => DurationListDict.Values;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Only applicable for StatusEffects with a duration or delay. Should the conditional checks only be done when the effect triggers,
|
/// Only applicable for StatusEffects with a duration or delay. Should the conditional checks only be done when the effect triggers,
|
||||||
@@ -1649,52 +1624,22 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-local list to avoid contention when cleaning up removed entities
|
private static readonly List<Entity> intervalsToRemove = new List<Entity>();
|
||||||
[ThreadStatic]
|
|
||||||
private static List<Entity> _threadLocalIntervalsToRemove;
|
|
||||||
|
|
||||||
private static List<Entity> IntervalsToRemove
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
_threadLocalIntervalsToRemove ??= new List<Entity>();
|
|
||||||
return _threadLocalIntervalsToRemove;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShouldWaitForInterval(Entity entity, float deltaTime)
|
public bool ShouldWaitForInterval(Entity entity, float deltaTime)
|
||||||
{
|
{
|
||||||
if (Interval > 0.0f && entity != null)
|
if (Interval > 0.0f && entity != null && intervalTimers != null)
|
||||||
{
|
{
|
||||||
// Thread-safe lazy initialization
|
if (intervalTimers.ContainsKey(entity))
|
||||||
if (intervalTimers == null)
|
|
||||||
{
|
{
|
||||||
Interlocked.CompareExchange(ref intervalTimers, new ConcurrentDictionary<Entity, float>(), null);
|
intervalTimers[entity] -= deltaTime;
|
||||||
|
if (intervalTimers[entity] > 0.0f) { return true; }
|
||||||
}
|
}
|
||||||
|
intervalsToRemove.Clear();
|
||||||
if (intervalTimers.TryGetValue(entity, out float currentTimer))
|
intervalsToRemove.AddRange(intervalTimers.Keys.Where(e => e.Removed));
|
||||||
|
foreach (var toRemove in intervalsToRemove)
|
||||||
{
|
{
|
||||||
float newTimer = currentTimer - deltaTime;
|
intervalTimers.Remove(toRemove);
|
||||||
if (newTimer > 0.0f)
|
|
||||||
{
|
|
||||||
intervalTimers.AddOrUpdate(entity, newTimer, (_, __) => newTimer);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up removed entities using thread-local list
|
|
||||||
var toRemove = IntervalsToRemove;
|
|
||||||
toRemove.Clear();
|
|
||||||
foreach (var key in intervalTimers.Keys)
|
|
||||||
{
|
|
||||||
if (key.Removed)
|
|
||||||
{
|
|
||||||
toRemove.Add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (var key in toRemove)
|
|
||||||
{
|
|
||||||
intervalTimers.TryRemove(key, out _);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1710,7 +1655,7 @@ namespace Barotrauma
|
|||||||
if (Duration > 0.0f && !Stackable)
|
if (Duration > 0.0f && !Stackable)
|
||||||
{
|
{
|
||||||
//ignore if not stackable and there's already an identical statuseffect
|
//ignore if not stackable and there's already an identical statuseffect
|
||||||
DurationListElement existingEffect = FindExistingDurationEffect(target);
|
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.FirstOrDefault() == target);
|
||||||
if (existingEffect != null)
|
if (existingEffect != null)
|
||||||
{
|
{
|
||||||
if (ResetDurationWhenReapplied)
|
if (ResetDurationWhenReapplied)
|
||||||
@@ -1721,74 +1666,30 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var targets = CurrentTargets;
|
currentTargets.Clear();
|
||||||
targets.Clear();
|
currentTargets.Add(target);
|
||||||
targets.Add(target);
|
if (!HasRequiredConditions(currentTargets)) { return; }
|
||||||
if (!HasRequiredConditions(targets)) { return; }
|
Apply(deltaTime, entity, currentTargets, worldPosition);
|
||||||
Apply(deltaTime, entity, targets, worldPosition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-local list to avoid contention when collecting targets
|
protected readonly List<ISerializableEntity> currentTargets = new List<ISerializableEntity>();
|
||||||
[ThreadStatic]
|
|
||||||
private static List<ISerializableEntity> _threadLocalCurrentTargets;
|
|
||||||
|
|
||||||
protected List<ISerializableEntity> CurrentTargets
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
_threadLocalCurrentTargets ??= new List<ISerializableEntity>();
|
|
||||||
return _threadLocalCurrentTargets;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe method to find an existing duration effect for a single target.
|
|
||||||
/// </summary>
|
|
||||||
private DurationListElement FindExistingDurationEffect(ISerializableEntity target)
|
|
||||||
{
|
|
||||||
foreach (var element in DurationListDict.Values)
|
|
||||||
{
|
|
||||||
if (element.Parent == this && element.Targets.FirstOrDefault() == target)
|
|
||||||
{
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Thread-safe method to find an existing duration effect for multiple targets.
|
|
||||||
/// </summary>
|
|
||||||
private DurationListElement FindExistingDurationEffect(IReadOnlyList<ISerializableEntity> targets)
|
|
||||||
{
|
|
||||||
foreach (var element in DurationListDict.Values)
|
|
||||||
{
|
|
||||||
if (element.Parent == this && element.Targets.SequenceEqual(targets))
|
|
||||||
{
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void Apply(ActionType type, float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition = null)
|
public virtual void Apply(ActionType type, float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Vector2? worldPosition = null)
|
||||||
{
|
{
|
||||||
if (Disabled) { return; }
|
if (Disabled) { return; }
|
||||||
if (this.type != type) { return; }
|
if (this.type != type) { return; }
|
||||||
if (ShouldWaitForInterval(entity, deltaTime)) { return; }
|
if (ShouldWaitForInterval(entity, deltaTime)) { return; }
|
||||||
|
|
||||||
var localTargets = CurrentTargets;
|
currentTargets.Clear();
|
||||||
localTargets.Clear();
|
|
||||||
foreach (ISerializableEntity target in targets)
|
foreach (ISerializableEntity target in targets)
|
||||||
{
|
{
|
||||||
if (!IsValidTarget(target)) { continue; }
|
if (!IsValidTarget(target)) { continue; }
|
||||||
localTargets.Add(target);
|
currentTargets.Add(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TargetIdentifiers != null && localTargets.Count == 0) { return; }
|
if (TargetIdentifiers != null && currentTargets.Count == 0) { return; }
|
||||||
|
|
||||||
bool hasRequiredItems = HasRequiredItems(entity);
|
bool hasRequiredItems = HasRequiredItems(entity);
|
||||||
if (!hasRequiredItems || !HasRequiredConditions(localTargets))
|
if (!hasRequiredItems || !HasRequiredConditions(currentTargets))
|
||||||
{
|
{
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
if (!hasRequiredItems && playSoundOnRequiredItemFailure)
|
if (!hasRequiredItems && playSoundOnRequiredItemFailure)
|
||||||
@@ -1802,15 +1703,15 @@ namespace Barotrauma
|
|||||||
if (Duration > 0.0f && !Stackable)
|
if (Duration > 0.0f && !Stackable)
|
||||||
{
|
{
|
||||||
//ignore if not stackable and there's already an identical statuseffect
|
//ignore if not stackable and there's already an identical statuseffect
|
||||||
DurationListElement existingEffect = FindExistingDurationEffect(localTargets);
|
DurationListElement existingEffect = DurationList.Find(d => d.Parent == this && d.Targets.SequenceEqual(currentTargets));
|
||||||
if (existingEffect != null)
|
if (existingEffect != null)
|
||||||
{
|
{
|
||||||
existingEffect.Reset(Math.Max(existingEffect.Timer, Duration), user);
|
existingEffect?.Reset(Math.Max(existingEffect.Timer, Duration), user);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Apply(deltaTime, entity, localTargets, worldPosition);
|
Apply(deltaTime, entity, currentTargets, worldPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Hull GetHull(Entity entity)
|
private Hull GetHull(Entity entity)
|
||||||
@@ -2023,8 +1924,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (Duration > 0.0f)
|
if (Duration > 0.0f)
|
||||||
{
|
{
|
||||||
var element = new DurationListElement(this, entity, targets, Duration, user);
|
DurationList.Add(new DurationListElement(this, entity, targets, Duration, user));
|
||||||
DurationListDict.TryAdd(element.Id, element);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2552,7 +2452,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (Interval > 0.0f && entity != null)
|
if (Interval > 0.0f && entity != null)
|
||||||
{
|
{
|
||||||
intervalTimers ??= new ConcurrentDictionary<Entity, float>();
|
intervalTimers ??= new Dictionary<Entity, float>();
|
||||||
intervalTimers[entity] = Interval;
|
intervalTimers[entity] = Interval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2949,15 +2849,13 @@ namespace Barotrauma
|
|||||||
UpdateAllProjSpecific(deltaTime);
|
UpdateAllProjSpecific(deltaTime);
|
||||||
|
|
||||||
DelayedEffect.Update(deltaTime);
|
DelayedEffect.Update(deltaTime);
|
||||||
|
for (int i = DurationList.Count - 1; i >= 0; i--)
|
||||||
// Thread-safe iteration over ConcurrentDictionary
|
|
||||||
foreach (var kvp in DurationListDict)
|
|
||||||
{
|
{
|
||||||
DurationListElement element = kvp.Value;
|
DurationListElement element = DurationList[i];
|
||||||
|
|
||||||
if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets))
|
if (element.Parent.CheckConditionalAlways && !element.Parent.HasRequiredConditions(element.Targets))
|
||||||
{
|
{
|
||||||
DurationListDict.TryRemove(element.Id, out _);
|
DurationList.RemoveAt(i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2966,7 +2864,7 @@ namespace Barotrauma
|
|||||||
(t is Limb limb && (limb.character == null || limb.character.Removed)));
|
(t is Limb limb && (limb.character == null || limb.character.Removed)));
|
||||||
if (element.Targets.Count == 0)
|
if (element.Targets.Count == 0)
|
||||||
{
|
{
|
||||||
DurationListDict.TryRemove(element.Id, out _);
|
DurationList.RemoveAt(i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3061,7 +2959,7 @@ namespace Barotrauma
|
|||||||
element.Timer -= deltaTime;
|
element.Timer -= deltaTime;
|
||||||
|
|
||||||
if (element.Timer > 0.0f) { continue; }
|
if (element.Timer > 0.0f) { continue; }
|
||||||
DurationListDict.TryRemove(element.Id, out _);
|
DurationList.Remove(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3161,8 +3059,8 @@ namespace Barotrauma
|
|||||||
public static void StopAll()
|
public static void StopAll()
|
||||||
{
|
{
|
||||||
CoroutineManager.StopCoroutines("statuseffect");
|
CoroutineManager.StopCoroutines("statuseffect");
|
||||||
DelayedEffect.DelayListDict.Clear();
|
DelayedEffect.DelayList.Clear();
|
||||||
DurationListDict.Clear();
|
DurationList.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddTag(Identifier tag)
|
public void AddTag(Identifier tag)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
@@ -76,7 +75,7 @@ namespace Barotrauma
|
|||||||
return !corrected;
|
return !corrected;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<string, string> cachedFileNames = new ConcurrentDictionary<string, string>();
|
private static readonly Dictionary<string, string> cachedFileNames = new Dictionary<string, string>();
|
||||||
|
|
||||||
public static string CorrectFilenameCase(string filename, out bool corrected, string directory = "")
|
public static string CorrectFilenameCase(string filename, out bool corrected, string directory = "")
|
||||||
{
|
{
|
||||||
@@ -154,7 +153,7 @@ namespace Barotrauma
|
|||||||
if (i < subDirs.Length - 1) { filename += "/"; }
|
if (i < subDirs.Length - 1) { filename += "/"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedFileNames.TryAdd(originalFilename, filename);
|
cachedFileNames.Add(originalFilename, filename);
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,26 +355,32 @@ namespace Barotrauma
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<string, List<string>> cachedLines = new ConcurrentDictionary<string, List<string>>();
|
private static Dictionary<string, List<string>> cachedLines = new Dictionary<string, List<string>>();
|
||||||
public static string GetRandomLine(string filePath, Rand.RandSync randSync = Rand.RandSync.ServerAndClient)
|
public static string GetRandomLine(string filePath, Rand.RandSync randSync = Rand.RandSync.ServerAndClient)
|
||||||
{
|
{
|
||||||
List<string> lines = cachedLines.GetOrAdd(filePath, path =>
|
List<string> lines;
|
||||||
|
if (cachedLines.ContainsKey(filePath))
|
||||||
|
{
|
||||||
|
lines = cachedLines[filePath];
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fileLines = File.ReadAllLines(path, catchUnauthorizedAccessExceptions: false).ToList();
|
lines = File.ReadAllLines(filePath, catchUnauthorizedAccessExceptions: false).ToList();
|
||||||
if (fileLines.Count == 0)
|
cachedLines.Add(filePath, lines);
|
||||||
|
if (lines.Count == 0)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError("File \"" + path + "\" is empty!");
|
DebugConsole.ThrowError("File \"" + filePath + "\" is empty!");
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
return fileLines;
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError("Couldn't open file \"" + path + "\"!", e);
|
DebugConsole.ThrowError("Couldn't open file \"" + filePath + "\"!", e);
|
||||||
return new List<string>();
|
return "";
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
if (lines.Count == 0) return "";
|
if (lines.Count == 0) return "";
|
||||||
return lines[Rand.Range(0, lines.Count, randSync)];
|
return lines[Rand.Range(0, lines.Count, randSync)];
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading;
|
|
||||||
using FarseerPhysics.Common;
|
using FarseerPhysics.Common;
|
||||||
using FarseerPhysics.Dynamics;
|
using FarseerPhysics.Dynamics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
@@ -75,15 +74,8 @@ namespace FarseerPhysics.Collision
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DynamicTree<T>
|
public class DynamicTree<T>
|
||||||
{
|
{
|
||||||
// Thread-local stacks to ensure thread safety during parallel queries/raycasts
|
private Stack<int> _raycastStack = new Stack<int>(256);
|
||||||
[ThreadStatic]
|
private Stack<int> _queryStack = new Stack<int>(256);
|
||||||
private static Stack<int> _raycastStack;
|
|
||||||
[ThreadStatic]
|
|
||||||
private static Stack<int> _queryStack;
|
|
||||||
|
|
||||||
private static Stack<int> RaycastStack => _raycastStack ??= new Stack<int>(256);
|
|
||||||
private static Stack<int> QueryStack => _queryStack ??= new Stack<int>(256);
|
|
||||||
|
|
||||||
private int _freeList;
|
private int _freeList;
|
||||||
private int _nodeCapacity;
|
private int _nodeCapacity;
|
||||||
private int _nodeCount;
|
private int _nodeCount;
|
||||||
@@ -354,12 +346,12 @@ namespace FarseerPhysics.Collision
|
|||||||
/// <param name="aabb">The aabb.</param>
|
/// <param name="aabb">The aabb.</param>
|
||||||
public void Query(Func<int, bool> callback, ref AABB aabb, ref Body body)
|
public void Query(Func<int, bool> callback, ref AABB aabb, ref Body body)
|
||||||
{
|
{
|
||||||
QueryStack.Clear();
|
_queryStack.Clear();
|
||||||
QueryStack.Push(_root);
|
_queryStack.Push(_root);
|
||||||
|
|
||||||
while (QueryStack.Count > 0)
|
while (_queryStack.Count > 0)
|
||||||
{
|
{
|
||||||
int nodeId = QueryStack.Pop();
|
int nodeId = _queryStack.Pop();
|
||||||
if (nodeId == NullNode)
|
if (nodeId == NullNode)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -394,8 +386,8 @@ namespace FarseerPhysics.Collision
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QueryStack.Push(node.Child1);
|
_queryStack.Push(node.Child1);
|
||||||
QueryStack.Push(node.Child2);
|
_queryStack.Push(node.Child2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -403,12 +395,12 @@ namespace FarseerPhysics.Collision
|
|||||||
|
|
||||||
public void Query(Func<int, bool> callback, ref AABB aabb)
|
public void Query(Func<int, bool> callback, ref AABB aabb)
|
||||||
{
|
{
|
||||||
QueryStack.Clear();
|
_queryStack.Clear();
|
||||||
QueryStack.Push(_root);
|
_queryStack.Push(_root);
|
||||||
|
|
||||||
while (QueryStack.Count > 0)
|
while (_queryStack.Count > 0)
|
||||||
{
|
{
|
||||||
int nodeId = QueryStack.Pop();
|
int nodeId = _queryStack.Pop();
|
||||||
if (nodeId == NullNode)
|
if (nodeId == NullNode)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -427,8 +419,8 @@ namespace FarseerPhysics.Collision
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QueryStack.Push(_nodes[nodeId].Child1);
|
_queryStack.Push(_nodes[nodeId].Child1);
|
||||||
QueryStack.Push(_nodes[nodeId].Child2);
|
_queryStack.Push(_nodes[nodeId].Child2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,12 +460,12 @@ namespace FarseerPhysics.Collision
|
|||||||
Vector2.Max(ref p1, ref t, out segmentAABB.UpperBound);
|
Vector2.Max(ref p1, ref t, out segmentAABB.UpperBound);
|
||||||
}
|
}
|
||||||
|
|
||||||
RaycastStack.Clear();
|
_raycastStack.Clear();
|
||||||
RaycastStack.Push(_root);
|
_raycastStack.Push(_root);
|
||||||
|
|
||||||
while (RaycastStack.Count > 0)
|
while (_raycastStack.Count > 0)
|
||||||
{
|
{
|
||||||
int nodeId = RaycastStack.Pop();
|
int nodeId = _raycastStack.Pop();
|
||||||
if (nodeId == NullNode)
|
if (nodeId == NullNode)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -530,8 +522,8 @@ namespace FarseerPhysics.Collision
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RaycastStack.Push(_nodes[nodeId].Child1);
|
_raycastStack.Push(_nodes[nodeId].Child1);
|
||||||
RaycastStack.Push(_nodes[nodeId].Child2);
|
_raycastStack.Push(_nodes[nodeId].Child2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# LuaCsForBarotrauma Enhanced Performance Project
|
# LuaCsForBarotrauma Enhanced Performence Project
|
||||||
|
|
||||||
> ⚠ **Warning:** This release is only available for server-side use and is not recommended to run on the client. Make sure that compatibility is adequately tested before deployment.
|
> ⚠ **Warning:** This release is only available for server-side use and is not recommended to run on the client. Make sure that compatibility is adequately tested before deployment.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user