From 210c7e312b16c5f5ab3aaead9f78bdd60d65397a Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Fri, 18 Sep 2015 00:35:15 +0100 Subject: [PATCH] Don't keep mix files in memory. Added SegmentStream class to assist in maintaining views into portions of the FileStream of the overall mix file to avoid having to copy sections into in-memory buffers. --- OpenRA.Game/FileSystem/MixFile.cs | 8 +- OpenRA.Game/OpenRA.Game.csproj | 1 + OpenRA.Game/Primitives/SegmentStream.cs | 102 ++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 OpenRA.Game/Primitives/SegmentStream.cs diff --git a/OpenRA.Game/FileSystem/MixFile.cs b/OpenRA.Game/FileSystem/MixFile.cs index f1da7bef18..31d8c9341e 100644 --- a/OpenRA.Game/FileSystem/MixFile.cs +++ b/OpenRA.Game/FileSystem/MixFile.cs @@ -14,6 +14,7 @@ using System.Globalization; using System.IO; using System.Linq; using OpenRA.FileFormats; +using OpenRA.Primitives; namespace OpenRA.FileSystem { @@ -179,9 +180,10 @@ namespace OpenRA.FileSystem if (!index.TryGetValue(hash, out e)) return null; - s.Seek(dataStart + e.Offset, SeekOrigin.Begin); - var data = s.ReadBytes((int)e.Length); - return new MemoryStream(data); + Stream parentStream; + var offset = dataStart + e.Offset + SegmentStream.GetOverallNestedOffset(s, out parentStream); + var path = ((FileStream)parentStream).Name; + return new SegmentStream(File.OpenRead(path), offset, e.Length); } public Stream GetContent(string filename) diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index fd235f027a..e937eb0d16 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -150,6 +150,7 @@ + diff --git a/OpenRA.Game/Primitives/SegmentStream.cs b/OpenRA.Game/Primitives/SegmentStream.cs new file mode 100644 index 0000000000..3877e762a5 --- /dev/null +++ b/OpenRA.Game/Primitives/SegmentStream.cs @@ -0,0 +1,102 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.ComponentModel; +using System.IO; + +namespace OpenRA.Primitives +{ + public class SegmentStream : Stream + { + public readonly Stream BaseStream; + public readonly long BaseOffset; + public readonly long BaseCount; + + public SegmentStream(Stream stream, long offset, long count) + { + if (stream == null) + throw new ArgumentNullException("stream"); + if (!stream.CanSeek) + throw new ArgumentException("stream must be seekable.", "stream"); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", "offset must be non-negative."); + if (count < 0) + throw new ArgumentOutOfRangeException("count", "count must be non-negative."); + + BaseStream = stream; + BaseOffset = offset; + BaseCount = count; + + stream.Seek(BaseOffset, SeekOrigin.Begin); + } + + public override bool CanSeek { get { return true; } } + public override bool CanRead { get { return BaseStream.CanRead; } } + public override bool CanWrite { get { return BaseStream.CanWrite; } } + + public override long Length { get { return BaseCount; } } + public override long Position + { + get { return BaseStream.Position - BaseOffset; } + set { BaseStream.Position = BaseOffset + value; } + } + + public override int Read(byte[] buffer, int offset, int count) { return BaseStream.Read(buffer, offset, count); } + public override void Write(byte[] buffer, int offset, int count) { BaseStream.Write(buffer, offset, count); } + public override void Flush() { BaseStream.Flush(); } + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + default: throw new InvalidEnumArgumentException("origin", (int)origin, typeof(SeekOrigin)); + case SeekOrigin.Begin: + return BaseStream.Seek(BaseOffset + offset, SeekOrigin.Begin) - offset; + case SeekOrigin.Current: + return BaseStream.Seek(offset, SeekOrigin.Current) - offset; + case SeekOrigin.End: + return BaseStream.Seek(offset, SeekOrigin.End) - offset; + } + } + + public override void SetLength(long value) { throw new NotSupportedException(); } + + public override bool CanTimeout { get { return BaseStream.CanTimeout; } } + + public override int ReadTimeout + { + get { return BaseStream.ReadTimeout; } + set { BaseStream.ReadTimeout = value; } + } + + public override int WriteTimeout + { + get { return BaseStream.WriteTimeout; } + set { BaseStream.WriteTimeout = value; } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + BaseStream.Dispose(); + base.Dispose(disposing); + } + + public static long GetOverallNestedOffset(Stream stream, out Stream overallBaseStream) + { + var offset = 0L; + overallBaseStream = stream; + var segmentStream = stream as SegmentStream; + if (segmentStream != null) + offset += segmentStream.BaseOffset + GetOverallNestedOffset(segmentStream.BaseStream, out overallBaseStream); + return offset; + } + } +}