Cleaned some code; added dynamic loading of sprites

This commit is contained in:
Brychan Dempsey 2021-08-29 21:12:37 +12:00
parent 9b5f08e86f
commit 453dcd52bd
3 changed files with 229 additions and 162 deletions

View File

@ -4,7 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" mc:Ignorable="d"
Title="PDG Server" Height="600" Width="800"> Title="PDG Server" Height="768" Width="1024" Background="#FF4B4B4B">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition/> <ColumnDefinition/>
@ -16,30 +16,33 @@
<ColumnDefinition/> <ColumnDefinition/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel> <StackPanel>
<TextBox x:Name="ImageSourceTextBox" Text="Source" TextWrapping="Wrap" LostFocus="ImageSourceTextBox_LostFocus"/> <TextBlock Text="Game Board File Path" TextWrapping="Wrap" Padding="0,10,0,0" Foreground="White" Margin="2,5,2,0"/>
<Button x:Name="BrowseButton" Content="Browse" Height="20" Width="40" Click="BrowseButton_Click"/> <TextBox x:Name="ImageSourceTextBox" TextWrapping="Wrap" LostFocus="ImageSourceTextBox_LostFocus" Margin="2,5,2,0"/>
<TextBlock Text="Number of Rows" TextWrapping="Wrap" Padding="0,10,0,0"/> <Button x:Name="BrowseButton" Content="Browse" Click="BrowseButton_Click" Margin="10,5,10,0"/>
<Slider x:Name="NumberOfRows" IsSnapToTickEnabled="True" Minimum="1" TickPlacement="BottomRight" AutoToolTipPlacement="BottomRight" ValueChanged="NumberOfRows_ValueChanged" Value="9"/> <TextBlock Text="Game Sprites Files" TextWrapping="Wrap" Padding="0,10,0,0" Foreground="White" Margin="2,0,2,0"/>
<TextBlock Text="Number of Columns:" TextWrapping="Wrap" Padding="0,10,0,0"/> <TextBox x:Name="ImageSourceTextBox_Copy" TextWrapping="Wrap" LostFocus="ImageSourceTextBox_LostFocus" Margin="2,5,2,0"/>
<Slider x:Name="NumberOfColumns" IsSnapToTickEnabled="True" Minimum="1" TickPlacement="BottomRight" AutoToolTipPlacement="BottomRight" ValueChanged="NumberOfColumns_ValueChanged" Value="9"/> <Button x:Name="GameSpritesBrowser" Content="Browse" Click="GameSpritesBrowser_Click" Margin="10,5,10,0"/>
<TextBlock Text="Starting Row" TextWrapping="Wrap" Padding="0,10,0,0"/> <TextBlock Text="Number of Rows" TextWrapping="Wrap" Padding="0,10,0,0" Foreground="White" Margin="2,0,2,0"/>
<Slider x:Name="StartingRow" IsSnapToTickEnabled="True" Value="8" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" ValueChanged="Sliders_ValueChanged" Maximum="9"/> <Slider x:Name="NumberOfRows" IsSnapToTickEnabled="True" Minimum="1" TickPlacement="BottomRight" AutoToolTipPlacement="BottomRight" ValueChanged="NumberOfRows_ValueChanged" Value="9" Margin="2,0,2,0"/>
<TextBlock Text="Starting Column" TextWrapping="Wrap" Padding="0,10,0,0"/> <TextBlock Text="Number of Columns:" TextWrapping="Wrap" Padding="0,10,0,0" Foreground="White" Margin="2,0,2,0"/>
<Slider x:Name="StartingColumn" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" ValueChanged="Sliders_ValueChanged" Maximum="9"/> <Slider x:Name="NumberOfColumns" IsSnapToTickEnabled="True" Minimum="1" TickPlacement="BottomRight" AutoToolTipPlacement="BottomRight" ValueChanged="NumberOfColumns_ValueChanged" Value="9" Margin="2,0,2,0"/>
<TextBlock Text="Zoom Box Starting Row" TextWrapping="Wrap" Padding="0,10,0,0"/> <TextBlock Text="Starting Row" TextWrapping="Wrap" Padding="0,10,0,0" Foreground="White" Margin="2,0,2,0"/>
<Slider x:Name="ZoomBoxStartRow" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" Value="3" ValueChanged="Sliders_ValueChanged" Maximum="9"/> <Slider x:Name="StartingRow" IsSnapToTickEnabled="True" Value="8" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" ValueChanged="Sliders_ValueChanged" Maximum="9" Margin="2,0,2,0"/>
<TextBlock Text="Zoom Box Starting Column" TextWrapping="Wrap" Padding="0,10,0,0"/> <TextBlock Text="Starting Column" TextWrapping="Wrap" Padding="0,10,0,0" Foreground="White" Margin="2,0,2,0"/>
<Slider x:Name="ZoomBoxStartColumn" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" Value="3" ValueChanged="Sliders_ValueChanged" Maximum="9"/> <Slider x:Name="StartingColumn" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" ValueChanged="Sliders_ValueChanged" Maximum="9" Margin="2,0,2,0"/>
<TextBlock Text="Zoom Box Span" TextWrapping="Wrap" Padding="0,10,0,0"/> <TextBlock Text="Zoom Box Starting Row" TextWrapping="Wrap" Padding="0,10,0,0" Foreground="White" Margin="2,0,2,0"/>
<Slider x:Name="ZoomBoxSpan" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" Value="3" ValueChanged="Sliders_ValueChanged" Minimum="1" Maximum="6"/> <Slider x:Name="ZoomBoxStartRow" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" Value="3" ValueChanged="Sliders_ValueChanged" Maximum="9" Margin="2,0,2,0"/>
<Button x:Name="GridVisibilityToggleButton" Content="Show/Hide Grid" Click="GridVisibilityToggleButton_Click"/> <TextBlock Text="Zoom Box Starting Column" TextWrapping="Wrap" Padding="0,10,0,0" Foreground="White" Margin="2,0,2,0"/>
<Button x:Name="StartServerButton" Content="Start Server" HorizontalAlignment="Center" Margin="0,25,0,0" Click="StartServerButton_Click"/> <Slider x:Name="ZoomBoxStartColumn" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" Value="3" ValueChanged="Sliders_ValueChanged" Maximum="9" Margin="2,0,2,0"/>
<Button x:Name="DrawRandomizedPlayers" Content="Draw Random Players" Margin="0,25,0,0" Click="DrawRandomizedPlayers_Click"/> <TextBlock Text="Zoom Box Span" TextWrapping="Wrap" Padding="0,10,0,0" Foreground="White" Margin="2,0,2,0"/>
<WrapPanel Height="100"> <Slider x:Name="ZoomBoxSpan" IsSnapToTickEnabled="True" AutoToolTipPlacement="BottomRight" TickPlacement="BottomRight" Value="3" ValueChanged="Sliders_ValueChanged" Minimum="1" Maximum="6" Margin="2,0,2,0"/>
</WrapPanel> <Button x:Name="GridVisibilityToggleButton" Content="Show/Hide Grid" Click="GridVisibilityToggleButton_Click" Margin="10,10,10,0"/>
<Button x:Name="StartServerButton" Content="Start Server" HorizontalAlignment="Stretch" Margin="10,25,10,0" Click="StartServerButton_Click" Background="#955FF17A" Foreground="White"/>
<Button x:Name="DrawRandomizedPlayers" Content="Draw Random Players" Margin="10,25,10,0" Click="DrawRandomizedPlayers_Click"/>
<UniformGrid x:Name="LoadedSprites" Columns="6" Margin="2,0,2,0" Rows="6" MinHeight="120" />
</StackPanel> </StackPanel>
<Border Grid.Column="1" BorderThickness="1,1,1,1" Background="#54D3D3D3" BorderBrush="DarkGray"> <Border Grid.Column="1" BorderThickness="1,1,1,1" BorderBrush="DarkGray" Background="#19000000">
<ListView x:Name="PlayersList"> <ListView x:Name="PlayersList" Background="{x:Null}">
<ListView.View> <ListView.View>
<GridView> <GridView>
<GridViewColumn/> <GridViewColumn/>

View File

@ -1,7 +1,10 @@
using Microsoft.Win32; using Microsoft.Win32;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
@ -22,7 +25,6 @@ namespace RBG_Server.WPF
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
//gameServer = new GameServer(8, 0, 9, 9, 3, 3, 3, 3, null);
} }
/// <summary> /// <summary>
@ -33,7 +35,7 @@ namespace RBG_Server.WPF
private void BrowseButton_Click(object sender, RoutedEventArgs e) private void BrowseButton_Click(object sender, RoutedEventArgs e)
{ {
OpenFileDialog ofd = new(); OpenFileDialog ofd = new();
ofd.Filter = "Image Files (.png; .jpg)|*.png;*.jpg"; ofd.Filter = "Image Files|*.png;*.jpg";
if (ofd.ShowDialog() == true) if (ofd.ShowDialog() == true)
{ {
ImageSourceTextBox.Text = ofd.FileName; ImageSourceTextBox.Text = ofd.FileName;
@ -81,7 +83,7 @@ namespace RBG_Server.WPF
/// <param name="e"></param> /// <param name="e"></param>
private void StartServerButton_Click(object sender, RoutedEventArgs e) private void StartServerButton_Click(object sender, RoutedEventArgs e)
{ {
gameServer = new((int)StartingRow.Value, (int)StartingColumn.Value, (int)NumberOfRows.Value, (int)NumberOfColumns.Value, (int)ZoomBoxStartRow.Value, (int)ZoomBoxStartColumn.Value, (int)ZoomBoxSpan.Value, (int)ZoomBoxSpan.Value, gameBoard); gameServer = new((byte)StartingRow.Value, (byte)StartingColumn.Value, (byte)NumberOfRows.Value, (byte)NumberOfColumns.Value, (byte)ZoomBoxStartRow.Value, (byte)ZoomBoxStartColumn.Value, (byte)ZoomBoxSpan.Value, (byte)ZoomBoxSpan.Value, gameBoard);
} }
@ -226,7 +228,7 @@ namespace RBG_Server.WPF
} }
private void GenerateNewGameBoard(List<Player> players) private void GenerateNewGameBoard(List<Player> players)
{ {
SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255)); SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255));
Grid[] cells = new Grid[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count]; Grid[] cells = new Grid[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count];
@ -285,6 +287,68 @@ namespace RBG_Server.WPF
GenerateGameBoard(randPlayers); GenerateGameBoard(randPlayers);
} }
ConcurrentDictionary<string, MemoryStream> loadedSprites = new();
Task loadingFiles;
private void GameSpritesBrowser_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog ofd = new();
ofd.Filter = "Image Files|*.png;*.jpg";
ofd.Multiselect = true;
if (ofd.ShowDialog() == true)
{
string[] resultFiles = ofd.FileNames;
loadedSprites.Clear();
// Run the following work in a separate thread
loadingFiles = Task.Run(() =>
{
// Continue in parallel
_ = Parallel.ForEach(resultFiles, (file) =>
{
MemoryStream ms = new();
using FileStream fs = File.OpenRead(file);
if (new FileInfo(file).Length > 512e6) // greater than 512 kB; resize the sprite to something more reasonable
{
BitmapImage gameBoardImage = new();
gameBoardImage.BeginInit();
gameBoardImage.StreamSource = fs;
gameBoardImage.CacheOption = BitmapCacheOption.Default;
gameBoardImage.EndInit();
double vRate = 128 / (double)gameBoardImage.PixelHeight; // Resize so the largest dimension is 128 px
double hRate = 128 / (double)gameBoardImage.PixelWidth;
TransformedBitmap modified = new(gameBoardImage, new ScaleTransform(gameBoardImage.PixelWidth * Math.Min(vRate, hRate), gameBoardImage.PixelHeight * Math.Min(vRate, hRate)));
PngBitmapEncoder encoder = new();
encoder.Frames.Add(BitmapFrame.Create(modified));
encoder.Save(ms);
}
else
{
fs.CopyTo(ms);
}
_ = loadedSprites.TryAdd(file, ms);
});
// Invoke the dispatcher to add all of the sprites to the loaded sprites list
_ = Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render, new Action(() =>
{
LoadedSprites.Children.Clear();
foreach (KeyValuePair<string, MemoryStream> item in loadedSprites)
{
BitmapImage image = new();
image.BeginInit();
image.StreamSource = item.Value;
image.EndInit();
Image spriteImage = new()
{
Source = image
};
_ = LoadedSprites.Children.Add(spriteImage);
}
}));
});
}
}
} }
} }

View File

@ -27,16 +27,16 @@ namespace RBG_Server
/// <param name="startRow">The starting row of the board</param> /// <param name="startRow">The starting row of the board</param>
/// <param name="startColumn">The starting column of the board</param> /// <param name="startColumn">The starting column of the board</param>
/// <param name="boardImage">The full-scale board image</param> /// <param name="boardImage">The full-scale board image</param>
public GameServer(int startRow, int startColumn, int rowSize, int columnSize, int zoomRow, int zoomCol, int zoomRowSpan, int zoomColSpan, Stream boardImage) public GameServer(byte startRow, byte startColumn, byte rowSize, byte columnSize, byte zoomRow, byte zoomCol, byte zoomRowSpan, byte zoomColSpan, Stream boardImage)
{ {
this.startRow = (byte)startRow; this.startRow = startRow;
this.startColumn = (byte)startColumn; this.startColumn = startColumn;
this.rowSize = (byte)rowSize; this.rowSize = rowSize;
this.columnSize = (byte)columnSize; this.columnSize = columnSize;
this.zoomRow = (byte)zoomRow; this.zoomRow = zoomRow;
this.zoomCol = (byte)zoomCol; this.zoomCol = zoomCol;
this.zoomRowSpan = (byte)zoomRowSpan; this.zoomRowSpan = zoomRowSpan;
this.zoomColSpan = (byte)zoomColSpan; this.zoomColSpan = zoomColSpan;
BitmapImage gameBoardImage = new(); BitmapImage gameBoardImage = new();
gameBoardImage.StreamSource = boardImage; gameBoardImage.StreamSource = boardImage;
// Hold images // Hold images
@ -194,7 +194,7 @@ namespace RBG_Server
stream.Write(boardImages[index]); stream.Write(boardImages[index]);
} }
static int currentTurn = 0; static int currentTurn;
void ServerUpdate() void ServerUpdate()
{ {
@ -224,132 +224,132 @@ namespace RBG_Server
// ***** Recieve Data ***** // ***** Recieve Data *****
// *************************** // ***************************
// Data are recieved in parallel, so that it may be processed quickly // Data are recieved in parallel, so that it may be processed quickly
Parallel.ForEach(clients, (item) => _ = Parallel.ForEach(clients, (item) =>
{ {
Player player = item.Value; Player player = item.Value;
if (!item.Key.Connected) if (!item.Key.Connected)
{ {
// Client no longer connected, add to the removed list // Client no longer connected, add to the removed list
closedClients.Enqueue(item.Key); closedClients.Enqueue(item.Key);
return; return;
} }
else else
{ {
// Game logic // Game logic
// If there are available bytes to be read, we can process them // If there are available bytes to be read, we can process them
if (item.Key.Available > 0) if (item.Key.Available > 0)
{ {
// Get number of bytes // Get number of bytes
int availableBytes = item.Key.Available; int availableBytes = item.Key.Available;
// Store read bytes in an array // Store read bytes in an array
byte[] receivedBytes = new byte[player.UnhandledBuffer.Length + availableBytes]; byte[] receivedBytes = new byte[player.UnhandledBuffer.Length + availableBytes];
// Grab the stream to read from/write to // Grab the stream to read from/write to
NetworkStream clientStream = item.Key.GetStream(); NetworkStream clientStream = item.Key.GetStream();
// read the buffer to the end // read the buffer to the end
clientStream.Read(receivedBytes, player.UnhandledBuffer.Length, availableBytes); _ = clientStream.Read(receivedBytes, player.UnhandledBuffer.Length, availableBytes);
// Copy the unhandled buffer to the recieved bytes // Copy the unhandled buffer to the recieved bytes
player.UnhandledBuffer.CopyTo(receivedBytes, 0); player.UnhandledBuffer.CopyTo(receivedBytes, 0);
// Reset the unhandled array // Reset the unhandled array
player.UnhandledBuffer = Array.Empty<byte>(); player.UnhandledBuffer = Array.Empty<byte>();
int pos = 0; int pos = 0;
// While we still haven't reached the end of the buffer, read bytes // While we still haven't reached the end of the buffer, read bytes
while (pos < receivedBytes.Length) while (pos < receivedBytes.Length)
{ {
if (pos == receivedBytes.Length - 1) if (pos == receivedBytes.Length - 1)
{ {
player.UnhandledBuffer = new byte[] { receivedBytes[^1] }; player.UnhandledBuffer = new byte[] { receivedBytes[^1] };
break; break;
} }
int command = receivedBytes[pos] >> 4; // Get the four command bits int command = receivedBytes[pos] >> 4; // Get the four command bits
if ((receivedBytes[pos++] & 0xF) != Players.IndexOf(item.Value)) if ((receivedBytes[pos++] & 0xF) != Players.IndexOf(item.Value))
{ {
Console.WriteLine("Unexpected client ID: {0}", (receivedBytes[pos++] & 0xF)); Console.WriteLine("Unexpected client ID: {0}", (receivedBytes[pos++] & 0xF));
} }
switch (command) switch (command)
{ {
case 0: // Heartbeat from client {0x0, 0x1, 0xFF} case 0: // Heartbeat from client {0x0, 0x1, 0xFF}
// In case of buffer overflow, take the last command and paste it into // In case of buffer overflow, take the last command and paste it into
// the unhandled buffer // the unhandled buffer
if (pos + 2 > receivedBytes.Length) if (pos + 2 > receivedBytes.Length)
{ {
player.UnhandledBuffer = new byte[receivedBytes.Length - pos + 1]; player.UnhandledBuffer = new byte[receivedBytes.Length - pos + 1];
for (int i = 0; i < receivedBytes.Length - pos + 1; i++) for (int i = 0; i < receivedBytes.Length - pos + 1; i++)
{ {
player.UnhandledBuffer[i] = receivedBytes[(pos - 1) + i]; player.UnhandledBuffer[i] = receivedBytes[(pos - 1) + i];
} }
// Finally, set the pos to the end of the buffer // Finally, set the pos to the end of the buffer
pos = receivedBytes.Length; pos = receivedBytes.Length;
} }
else else
{ {
// Buffer won't overflow, process normally // Buffer won't overflow, process normally
player.LastTime = DateTime.Now.Ticks; player.LastTime = DateTime.Now.Ticks;
pos += 2; // Move two bytes pos += 2; // Move two bytes
} }
break; break;
case 1: // Player sets name case 1: // Player sets name
List<byte> strBytes = new(); List<byte> strBytes = new();
bool endReached = false; bool endReached = false;
while (pos < receivedBytes.Length && receivedBytes[pos] != 0xFF) while (pos < receivedBytes.Length && receivedBytes[pos] != 0xFF)
{ {
byte nextByte = receivedBytes[pos++]; byte nextByte = receivedBytes[pos++];
if (pos != receivedBytes.Length - 1 && receivedBytes[pos + 1] == 0xFF) if (pos != receivedBytes.Length - 1 && receivedBytes[pos + 1] == 0xFF)
{ {
endReached = true; endReached = true;
} }
else else
{ {
strBytes.Add(nextByte); strBytes.Add(nextByte);
} }
} }
if (endReached) if (endReached)
{ {
// Buffer contained full string, move to the next command // Buffer contained full string, move to the next command
player.PlayerName = Encoding.UTF8.GetString(strBytes.ToArray()); player.PlayerName = Encoding.UTF8.GetString(strBytes.ToArray());
} }
else else
{ {
player.UnhandledBuffer = new byte[receivedBytes.Length - strBytes.Count + 1]; player.UnhandledBuffer = new byte[receivedBytes.Length - strBytes.Count + 1];
for (int i = 0; i < receivedBytes.Length - strBytes.Count + 1; i++) for (int i = 0; i < receivedBytes.Length - strBytes.Count + 1; i++)
{ {
player.UnhandledBuffer[i] = receivedBytes[(strBytes.Count - 1) + i]; player.UnhandledBuffer[i] = receivedBytes[(strBytes.Count - 1) + i];
} }
// Pos is implicitely // Pos is implicitely
} }
break; break;
case 2: // update player position case 2: // update player position
if (Players[currentTurn].Equals(player)) if (Players[currentTurn].Equals(player))
{ {
// It is our turn, move // It is our turn, move
byte nextByte = receivedBytes[pos++]; byte nextByte = receivedBytes[pos++];
int row = nextByte >> 4; int row = nextByte >> 4;
int column = nextByte & 0xF; int column = nextByte & 0xF;
player.Column = column; player.Column = column;
player.Row = row; player.Row = row;
// Can't update turn until we are synchronised, else we would // Can't update turn until we are synchronised, else we would
// inadvertently accept turns from the next player // inadvertently accept turns from the next player
turnCompleted = true; turnCompleted = true;
} }
break; break;
default: default:
Console.WriteLine("Unknown client command"); Console.WriteLine("Unknown client command");
break; break;
} }
} }
} }
// If we haven't recieved data from this client for some time, send a packet that requests // If we haven't recieved data from this client for some time, send a packet that requests
// a response // a response
else if (TimeSpan.FromTicks(DateTime.Now.Ticks - item.Value.LastTime) > TimeSpan.FromSeconds(5)) else if (TimeSpan.FromTicks(DateTime.Now.Ticks - item.Value.LastTime) > TimeSpan.FromSeconds(5))
{ {
item.Key.GetStream().Write(new byte[] { (byte)(Players.IndexOf(item.Value)), 0xFF }); // Heartbeat is 0x0, 0x1 item.Key.GetStream().Write(new byte[] { (byte)Players.IndexOf(item.Value), 0xFF }); // Heartbeat is 0x0, 0x1
Player curr = item.Value; Player curr = item.Value;
curr.LastTime = DateTime.Now.Ticks; curr.LastTime = DateTime.Now.Ticks;
} }
} }
}); });
// Synchronised, update player turn // Synchronised, update player turn
if (turnCompleted) if (turnCompleted)
{ {
@ -359,7 +359,7 @@ namespace RBG_Server
// *************************** // ***************************
// ** Remove old connections** // ** Remove old connections**
// *************************** // ***************************
foreach (var item in closedClients) foreach (TcpClient item in closedClients)
{ {
_ = Players.Remove(clients[item]); _ = Players.Remove(clients[item]);
_ = clients.Remove(item); _ = clients.Remove(item);