SanAndreasUnity/Assets/Scripts/Importing/Archive/ArchiveManager.cs
in0finite 32c0be1af2
new world loading system (#110)
* wip

* much faster world creation

* add StaticGeometryInspector

* disable child/parent logic and fading

* rename

* (de)activate objects based on parent

* set draw distance based on layers

* ...

* wip

* wip

* wip

* remove unused param

* prevent concurrent modification

* ...

* catch exceptions when notifying

* ...

* notify about area, not objects

* limit public access to Area

* ...

* ...

* allow public access

* add public api

* adapt code

* pass callback to ctor

* adapt focus points

* fix

* fix intersection check

* support rectangles

* adjust parameters in prefab

* this should fix IsInsideOf()

* ...

* ...

* fix getting area by index

* create area if not exists

* ...

* ...

* ...

* wip for distance levels

* remove constraint on generic parameter

* add some validation

* fix

* fix draw distance per level

* change time of day in which lights are visible

* add todos

* don't use id for UnRegisterFocusPoint()

* use hash set for storing focus points

* add 1 more level

* mark area for update only if visibility changes

* profile WorldSystem calls

* add some profiling sections

* limit time per frame for LoadingThread

* switch custom concurrent queue

* copy jobs to buffer

* rename

* change max draw distance setting

* wait one more frame

* try to remove 801 distance level to remove holes

* attempt to hide interiors, but failed

* delete no longer needed script

* optimization

* some error checking

* add camera as focus point

* dont add camera as focus point in headless mode

* working on load priority

* fix bug - load priority finished

* ...

* small optimizations

* ...

* ...

* remove unneeded variable

* add fading

* dont do fading in headless mode

* fadeRate available in inspector

* change fade rate

* take into account if geometry is loaded when checking if object should be visible, and if fading should be done

* small optimization

* cache IsInHeadlessMode

* display Instance info in inspector

* move interiors up in the sky

* rename

* adapt code to different y pos of interiors

* refactor

* fix finding matched enex for enexes that lead to the same interior level

* display new world stats

* rename

* rename class

* ...

* ...

* extract function

* extract parameters into a struct

* add focus point to dead body

* add focus point to vehicle

* add focus point to vehicle detached parts

* remove OutOfRangeDestroyer from vehicle, and destroy vehicle if it falls below the map

* dont use focus points on vehicle and vehicle detached parts, when not on server

* add focus point for npc peds

* add possibility to set timeout during which focus point keeps revealing after it's destroyed

* adapt UnRegisterFocusPoint() to timeout

* rename

* adapt code

* cleanup MapObject class

* ...

* converting to `lock()`

* optimize method: use 1 lock instead of 3

* call OnObjectFinishedLoading() instead of AddToLoadedObjects()

* ...

* make sure it's main thread

* AsyncLoader is no longer thread safe

* convert static members to non-static in LoadingThread

* fix

* ...

* store indexes for each area

* impl GetAreaCenter()

* calculate load priority based on distance to area, not objects ; limit time per frame ; sort area in Cell, not in concurrent SortedSet ;

* add support for changing draw distance at runtime

* delay setting the new value by 0.2 s

* have a separate default max draw distance for mobile platforms

* adjust y axis world params so that number of visible areas is reduced

* remove "camera far clip plane" setting

* rename

* document flags

* rename

* disable shadow casting and receiving for some objects

* allow casting shadows for LODs with large draw distance

* remove "WorldSystem" layer

* revert layer
2021-07-18 06:03:43 +02:00

179 lines
No EOL
6 KiB
C#

using SanAndreasUnity.Importing.RenderWareStream;
using SanAndreasUnity.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using System.Runtime.CompilerServices;
namespace SanAndreasUnity.Importing.Archive
{
public interface IArchive
{
IEnumerable<string> GetAllFiles();
IEnumerable<string> GetFileNamesWithExtension(string ext);
bool ContainsFile(string name);
Stream ReadFile(string name);
int NumLoadedEntries { get; }
}
/// <summary>
/// Handles archive loading and reading. You should never read from archives manually, but always use this class, because it provides thread safety.
/// </summary>
public static class ArchiveManager
{
public static string ModelsDir { get { return Path.Combine(Config.GamePath, "models"); } }
public static string DataDir { get { return Path.Combine(Config.GamePath, "data"); } }
public static string GetPath(params string[] relative)
{
return relative.Aggregate(Config.GamePath, Path.Combine).Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static string GetCaseSensitiveFilePath(string fileName)
{
string filePath = null;
foreach(var archive in _sLoadedArchives.OfType<LooseArchive>())
{
if (archive.GetFilePath(fileName, ref filePath))
return filePath;
}
throw new FileNotFoundException(fileName);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static string PathToCaseSensitivePath(string path)
{
return ArchiveManager.GetCaseSensitiveFilePath(Path.GetFileName(path));
}
private static readonly List<IArchive> _sLoadedArchives = new List<IArchive>();
[MethodImpl(MethodImplOptions.Synchronized)]
public static int GetNumArchives()
{
return _sLoadedArchives.Count;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static int GetTotalNumLoadedEntries()
{
return _sLoadedArchives.Sum(a => a.NumLoadedEntries);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static List<string> GetAllEntries()
{
return _sLoadedArchives
.SelectMany(a => a.GetAllFiles())
.ToList();
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static LooseArchive LoadLooseArchive(string dirPath)
{
var arch = LooseArchive.Load(dirPath);
_sLoadedArchives.Add(arch);
return arch;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static ImageArchive LoadImageArchive(string filePath)
{
var arch = ImageArchive.Load(filePath);
_sLoadedArchives.Add(arch);
return arch;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static bool FileExists(string name)
{
return _sLoadedArchives.Any(x => x.ContainsFile(name));
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static void GetFileNamesWithExtension(string ext, List<string> fileNames)
{
foreach (var archive in _sLoadedArchives)
fileNames.AddRange(archive.GetFileNamesWithExtension(ext));
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static List<string> GetFileNamesWithExtension(string ext)
{
var list = new List<string>();
GetFileNamesWithExtension(ext, list);
return list;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static List<string> GetFilePathsFromLooseArchivesWithExtension(string ext)
{
var list = new List<string>();
foreach (var archive in _sLoadedArchives.OfType<LooseArchive>())
{
foreach (string fileName in archive.GetFileNamesWithExtension(ext))
{
string filePath = null;
archive.GetFilePath(fileName, ref filePath);
list.Add(filePath);
}
}
return list;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static Stream ReadFile(string name)
{
var arch = _sLoadedArchives.FirstOrDefault(x => x.ContainsFile(name));
if (arch == null) throw new FileNotFoundException(name);
// get a stream and build memory stream out of it - this will ensure thread safe access
var stream = arch.ReadFile(name);
byte[] buffer = new byte[stream.Length];
stream.Read (buffer, 0, (int) stream.Length);
stream.Dispose ();
return new MemoryStream (buffer);
}
// this method should not be synchronized, because thread would block while
// archive is being read, but the thread only wants to register a job and continue
// [MethodImpl(MethodImplOptions.Synchronized)]
public static void ReadFileAsync(string name, float loadPriority, System.Action<Stream> onFinish)
{
Behaviours.LoadingThread.RegisterJob (new Behaviours.LoadingThread.Job<Stream> () {
priority = loadPriority,
action = () => ReadFile( name ),
callbackFinish = (stream) => { onFinish(stream); },
});
}
[MethodImpl(MethodImplOptions.Synchronized)] // ensure section is read, before another thread can read archives
public static TSection ReadFile<TSection>(string name)
where TSection : SectionData
{
using (var stream = ReadFile(name))
{
var section = Section<SectionData>.ReadData(stream) as TSection;
if (section == null)
{
throw new ArgumentException(string.Format("File \"{0}\" is not a {1}!", name, typeof(TSection).Name), "name");
}
return section;
}
}
}
}