Files
FNF-i486-Engine/source/Paths.hx
JordanSantiagoYT 4eb6376986 peak
Added Rainbow FPS
Fixed FPS counter not updating when on 60 FPS
Sort of GC improvements
barely any performance improvements
2023-11-17 19:45:04 -05:00

692 lines
20 KiB
Haxe

package;
import animateatlas.AtlasFrameMaker;
import flixel.graphics.frames.FlxFrame.FlxFrameAngle;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.graphics.FlxGraphic;
import flixel.math.FlxPoint;
import flixel.math.FlxRect;
import flixel.FlxG;
import openfl.display.BitmapData;
import openfl.display3D.textures.RectangleTexture;
import openfl.utils.AssetType;
import openfl.utils.Assets as OpenFlAssets;
import openfl.system.System;
import openfl.geom.Rectangle;
import lime.utils.Assets;
import flash.media.Sound;
#if sys
import sys.io.File;
import sys.FileSystem;
#end
import flixel.graphics.FlxGraphic;
import openfl.display.BitmapData;
import haxe.Json;
import openfl.display3D.textures.RectangleTexture;
import lime.media.vorbis.VorbisFile;
import lime.media.AudioBuffer;
import flash.media.Sound;
#if cpp
import cpp.vm.Gc;
#elseif hl
import hl.Gc;
#elseif neko
import neko.vm.Gc;
#end
using StringTools;
class Paths
{
inline public static var SOUND_EXT = #if web "mp3" #else "ogg" #end;
inline public static var VIDEO_EXT = "mp4";
#if MODS_ALLOWED
public static var ignoreModFolders:Array<String> = [
'characters',
'custom_events',
'custom_notetypes',
'data',
'songs',
'music',
'sounds',
'shaders',
'videos',
'images',
'stages',
'weeks',
'fonts',
'scripts',
'achievements'
];
#end
public static function excludeAsset(key:String) {
if (!dumpExclusions.contains(key))
dumpExclusions.push(key);
}
public static var dumpExclusions:Array<String> =
[
SUtil.getPath() + 'assets/music/freakyMenu.$SOUND_EXT',
SUtil.getPath() + 'assets/shared/music/breakfast.$SOUND_EXT',
SUtil.getPath() + 'assets/shared/music/tea-time.$SOUND_EXT',
];
@:noCompletion private inline static function _gc(major:Bool) {
#if (cpp || neko)
Gc.run(major);
#elseif hl
Gc.major();
#end
}
@:noCompletion public inline static function compress() {
#if cpp
Gc.compact();
#elseif hl
Gc.major();
#elseif neko
Gc.run(true);
#end
}
public inline static function gc(major:Bool = false, repeat:Int = 1) {
while(repeat-- > 0) _gc(major);
}
/// haya I love you for the base cache dump I took to the max
public static function clearUnusedMemory() {
// clear non local assets in the tracked assets list
for (key in currentTrackedAssets.keys()) {
// if it is not currently contained within the used local assets
if (!localTrackedAssets.contains(key) && !dumpExclusions.contains(key)) {
var obj = currentTrackedAssets.get(key);
@:privateAccess
if (obj != null) {
// remove the key from all cache maps
FlxG.bitmap._cache.remove(key);
openfl.Assets.cache.removeBitmapData(key);
currentTrackedAssets.remove(key);
// and get rid of the object
obj.persist = false; // make sure the garbage collector actually clears it up
obj.destroyOnNoUse = true;
obj.destroy();
}
}
}
// run the garbage collector for good measure lmfao
compress();
gc(true);
}
// define the locally tracked assets
public static var localTrackedAssets:Array<String> = [];
public static function clearStoredMemory(?cleanUnused:Bool = false) {
// clear anything not in the tracked assets list
@:privateAccess
for (key in FlxG.bitmap._cache.keys())
{
var obj = FlxG.bitmap._cache.get(key);
if (obj != null && !currentTrackedAssets.exists(key)) {
openfl.Assets.cache.removeBitmapData(key);
FlxG.bitmap._cache.remove(key);
obj.destroy();
}
}
// clear all sounds that are cached
for (key in currentTrackedSounds.keys()) {
if (!localTrackedAssets.contains(key)
&& !dumpExclusions.contains(key) && key != null) {
//trace('test: ' + dumpExclusions, key);
Assets.cache.clear(key);
currentTrackedSounds.remove(key);
}
}
// flags everything to be cleared out next unused memory clear
localTrackedAssets = [];
#if !html5 openfl.Assets.cache.clear("songs"); #end
gc(true);
compress();
}
static public var currentModDirectory:String = '';
static public var currentLevel:String;
static public function setCurrentLevel(name:String)
{
currentLevel = name.toLowerCase();
}
public static function getPath(file:String, type:AssetType, ?library:Null<String> = null)
{
if (library != null)
return getLibraryPath(file, library);
if (currentLevel != null)
{
var levelPath:String = '';
if(currentLevel != 'shared') {
levelPath = getLibraryPathForce(file, currentLevel);
if (OpenFlAssets.exists(levelPath, type))
return levelPath;
}
levelPath = getLibraryPathForce(file, "shared");
if (OpenFlAssets.exists(levelPath, type))
return levelPath;
}
return getPreloadPath(file);
}
static public function getLibraryPath(file:String, library = "preload")
{
return if (library == "preload" || library == "default") getPreloadPath(file); else getLibraryPathForce(file, library);
}
inline static function getLibraryPathForce(file:String, library:String)
{
var returnPath = '$library:assets/$library/$file';
return returnPath;
}
inline public static function getPreloadPath(file:String = '')
{
return 'assets/$file';
}
inline static public function file(file:String, type:AssetType = TEXT, ?library:String)
{
return getPath(file, type, library);
}
inline static public function txt(key:String, ?library:String)
{
return getPath('data/$key.txt', TEXT, library);
}
inline static public function xml(key:String, ?library:String)
{
return getPath('data/$key.xml', TEXT, library);
}
inline static public function json(key:String, ?library:String)
{
return getPath('data/' + key + '.json', TEXT, library);
}
inline static public function shaderFragment(key:String, ?library:String)
{
return getPath('shaders/$key.frag', TEXT, library);
}
inline static public function shaderVertex(key:String, ?library:String)
{
return getPath('shaders/$key.vert', TEXT, library);
}
inline static public function lua(key:String, ?library:String)
{
return getPath('$key.lua', TEXT, library);
}
//Video loading (part of it)
static public function video(key:String)
{
#if MODS_ALLOWED
var file:String = modsVideo(key);
if(FileSystem.exists(file)) {
return file;
}
#end
return SUtil.getPath() + 'assets/videos/$key.$VIDEO_EXT';
}
//Sound loading.
static public function sound(key:String, ?library:String):Sound
{
var sound:Sound = returnSound('sounds', key, library);
return sound;
}
//Random sound loading.
inline static public function soundRandom(key:String, min:Int, max:Int, ?library:String)
{
return sound(key + FlxG.random.int(min, max), library);
}
//Music loading. Loads anything in assets/data/music, OR mods/data/music (if mods are allowed)
inline static public function music(key:String, ?library:String):Sound
{
var file:Sound = returnSound('music', key, library);
return file;
}
//Loads the Voices. Crucial for generateSong
inline static public function voices(song:String):Any
{
#if html5
return 'songs:assets/songs/${formatToSongPath(song)}/Voices.$SOUND_EXT';
#else
var songKey:String = '${formatToSongPath(song)}/Voices';
var voices = returnSound('songs', songKey);
if (!ClientPrefs.progAudioLoad) voices = returnSoundFull('songs', songKey);
return voices;
#end
}
//Loads the instrumental. Crucial for generateSong
inline static public function inst(song:String):Any
{
#if html5
return 'songs:assets/songs/${formatToSongPath(song)}/Inst.$SOUND_EXT';
#else
var songKey:String = '${formatToSongPath(song)}/Inst';
var inst = returnSound('songs', songKey);
if (!ClientPrefs.progAudioLoad) inst = returnSoundFull('songs', songKey);
return inst;
#end
}
//Difficulty-specific Inst and Voices loading. Doesn't work so i've scrapped it for now but this was taken directly from Leather Engine
/*
static public function voices(song:String, ?difficulty:String)
{
if(difficulty != null)
{
if(Assets.exists('songs:assets/songs/${song.toLowerCase()}/Voices-$difficulty.$SOUND_EXT'))
{
return 'songs:assets/songs/${song.toLowerCase()}/Voices-$difficulty.$SOUND_EXT';
}
}
return 'songs:assets/songs/${song.toLowerCase()}/Voices.$SOUND_EXT';
}
static public function inst(song:String, ?difficulty:String)
{
if(difficulty != null)
{
if(Assets.exists('songs:assets/songs/${song.toLowerCase()}/Inst-$difficulty.$SOUND_EXT'))
{
return 'songs:assets/songs/${song.toLowerCase()}/Inst-$difficulty.$SOUND_EXT';
}
}
return 'songs:assets/songs/${song.toLowerCase()}/Inst.$SOUND_EXT';
}
*/
//Loads images.
public static var currentTrackedAssets:Map<String, FlxGraphic> = [];
static public function image(key:String, ?library:String = null, ?allowGPU:Bool = true):FlxGraphic
{
if (ClientPrefs.cacheOnGPU) {
var bitmap:BitmapData = null;
var file:String = null;
#if MODS_ALLOWED
file = modsImages(key);
if (currentTrackedAssets.exists(file))
{
localTrackedAssets.push(file);
return currentTrackedAssets.get(file);
}
else if (FileSystem.exists(file))
bitmap = BitmapData.fromFile(file);
else
#end
{
file = getPath('images/$key.png', IMAGE, library);
if (currentTrackedAssets.exists(file))
{
localTrackedAssets.push(file);
return currentTrackedAssets.get(file);
}
else if (OpenFlAssets.exists(file, IMAGE))
bitmap = OpenFlAssets.getBitmapData(file);
}
if (bitmap != null)
{
localTrackedAssets.push(file);
if (allowGPU && ClientPrefs.cacheOnGPU)
{
var texture:RectangleTexture = FlxG.stage.context3D.createRectangleTexture(bitmap.width, bitmap.height, BGRA, true);
texture.uploadFromBitmapData(bitmap);
bitmap.image.data = null;
bitmap.dispose();
bitmap.disposeImage();
bitmap = BitmapData.fromTexture(texture);
}
var newGraphic:FlxGraphic = FlxGraphic.fromBitmapData(bitmap, false, file);
newGraphic.persist = true;
newGraphic.destroyOnNoUse = false;
currentTrackedAssets.set(file, newGraphic);
return newGraphic;
}
trace('oh no its returning null NOOOO ($file)');
return null;
} else {
#if MODS_ALLOWED
var modKey:String = modsImages(key);
if(FileSystem.exists(modKey)) {
if(!currentTrackedAssets.exists(modKey)) {
var newBitmap:BitmapData = BitmapData.fromFile(modKey);
var newGraphic:FlxGraphic = FlxGraphic.fromBitmapData(newBitmap, false, modKey);
newGraphic.persist = true;
currentTrackedAssets.set(modKey, newGraphic);
}
localTrackedAssets.push(modKey);
return currentTrackedAssets.get(modKey);
}
#end
var path = getPath('images/$key.png', IMAGE, library);
//trace(path);
if (OpenFlAssets.exists(path, IMAGE)) {
if(!currentTrackedAssets.exists(path)) {
var newGraphic:FlxGraphic = FlxG.bitmap.add(path, false, path);
newGraphic.persist = true;
currentTrackedAssets.set(path, newGraphic);
}
localTrackedAssets.push(path);
return currentTrackedAssets.get(path);
}
trace('oh no its returning null NOOOO (' + modKey + ')');
return null;
}
}
static public function getTextFromFile(key:String, ?ignoreMods:Bool = false):String
{
#if sys
#if MODS_ALLOWED
if (!ignoreMods && FileSystem.exists(modFolders(key)))
return File.getContent(modFolders(key));
#end
if (FileSystem.exists(SUtil.getPath() + getPreloadPath(key)))
return File.getContent(SUtil.getPath() + getPreloadPath(key));
if (currentLevel != null)
{
var levelPath:String = '';
if(currentLevel != 'shared') {
levelPath = SUtil.getPath() + getLibraryPathForce(key, currentLevel);
if (FileSystem.exists(levelPath))
return File.getContent(levelPath);
}
levelPath = SUtil.getPath() + getLibraryPathForce(key, 'shared');
if (FileSystem.exists(levelPath))
return File.getContent(levelPath);
}
#end
return Assets.getText(getPath(key, TEXT));
}
inline static public function font(key:String)
{
#if MODS_ALLOWED
var file:String = modsFont(key);
if(FileSystem.exists(file)) {
return file;
}
#end
return SUtil.getPath() + 'assets/fonts/$key';
}
public static function fileExists(key:String, type:AssetType, ?ignoreMods:Bool = false, ?library:String)
{
#if MODS_ALLOWED
if(FileSystem.exists(mods(currentModDirectory + '/' + key)) || FileSystem.exists(mods(key))) {
return true;
}
#end
if(OpenFlAssets.exists(getPath(key, type))) {
return true;
}
return false;
}
inline static public function getSparrowAtlas(key:String, ?library:String):FlxAtlasFrames
{
#if MODS_ALLOWED
var imageLoaded:FlxGraphic = image(key);
var xmlExists:Bool = false;
if(FileSystem.exists(modsXml(key))) {
xmlExists = true;
}
return FlxAtlasFrames.fromSparrow((imageLoaded != null ? imageLoaded : image(key, library)), (xmlExists ? File.getContent(modsXml(key)) : file('images/$key.xml', library)));
#else
return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library));
#end
}
inline static public function getPackerAtlas(key:String, ?library:String)
{
#if MODS_ALLOWED
var imageLoaded:FlxGraphic = image(key);
var txtExists:Bool = false;
if(FileSystem.exists(modsTxt(key))) {
txtExists = true;
}
return FlxAtlasFrames.fromSpriteSheetPacker((imageLoaded != null ? imageLoaded : image(key, library)), (txtExists ? File.getContent(modsTxt(key)) : file('images/$key.txt', library)));
#else
return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library), file('images/$key.txt', library));
#end
}
inline static public function formatToSongPath(path:String) {
var invalidChars = ~/[~&\\;:<>#]/;
var hideChars = ~/[.,'"%?!]/;
var path = invalidChars.split(path.replace(' ', '-')).join("-");
return hideChars.split(path).join("").toLowerCase();
}
// completely rewritten asset loading? fuck!
public static var currentTrackedSounds:Map<String, Sound> = [];
//Returns sounds which is useful for all the sfx
public static function returnSound(path:String, key:String, ?library:String, stream:Bool = false) {
var sound:Sound = null;
var file:String = null;
#if MODS_ALLOWED
file = modsSounds(path, key);
if (currentTrackedSounds.exists(file)) {
localTrackedAssets.push(file);
return currentTrackedSounds.get(file);
} else if (FileSystem.exists(file)) {
#if lime_vorbis
if (stream)
sound = Sound.fromAudioBuffer(AudioBuffer.fromVorbisFile(VorbisFile.fromFile(file)));
else
#end
sound = Sound.fromFile(file);
}
else
#end
{
// I hate this so god damn much
var gottenPath:String = getPath('$path/$key.$SOUND_EXT', SOUND, library);
file = gottenPath.substring(gottenPath.indexOf(':') + 1, gottenPath.length);
if (path == 'songs')
gottenPath = 'songs:' + gottenPath;
if (currentTrackedSounds.exists(file))
{
localTrackedAssets.push(file);
return currentTrackedSounds.get(file);
}
else if (OpenFlAssets.exists(gottenPath, SOUND))
{
#if lime_vorbis
if (stream)
sound = OpenFlAssets.getMusic(gottenPath);
else
#end
sound = OpenFlAssets.getSound(gottenPath);
}
}
if (sound != null)
{
localTrackedAssets.push(file);
currentTrackedSounds.set(file, sound);
return sound;
}
trace('oh no its returning null NOOOO ($file)');
return null;
}
//Ditto, but returns the full sound instead
public static function returnSoundFull(path:String, key:String, ?library:String) {
#if MODS_ALLOWED
var file:String = modsSounds(path, key);
if(FileSystem.exists(file)) {
if(!currentTrackedSounds.exists(file)) {
currentTrackedSounds.set(file, Sound.fromFile(file));
}
localTrackedAssets.push(key);
return currentTrackedSounds.get(file);
}
#end
// I hate this so god damn much
var gottenPath:String = getPath('$path/$key.$SOUND_EXT', SOUND, library);
gottenPath = gottenPath.substring(gottenPath.indexOf(':') + 1, gottenPath.length);
// trace(gottenPath);
if(!currentTrackedSounds.exists(gottenPath))
#if MODS_ALLOWED
currentTrackedSounds.set(gottenPath, Sound.fromFile('./' + gottenPath));
#else
{
var folder:String = '';
if(path == 'songs') folder = 'songs:';
currentTrackedSounds.set(gottenPath, OpenFlAssets.getSound(folder + getPath('$path/$key.$SOUND_EXT', SOUND, library)));
}
#end
localTrackedAssets.push(gottenPath);
return currentTrackedSounds.get(gottenPath);
}
#if MODS_ALLOWED
//Loads mods.
inline static public function mods(key:String = '') {
return SUtil.getPath() + 'mods/' + key;
}
//Loads fonts in mods/fonts.
inline static public function modsFont(key:String) {
return modFolders('fonts/' + key);
}
//Loads jsons in mods/data.
inline static public function modsJson(key:String) {
return modFolders('data/' + key + '.json');
}
//Loads videos in mods/videos.
inline static public function modsVideo(key:String) {
return modFolders('videos/' + key + '.' + VIDEO_EXT);
}
//Loads sounds in mods/sounds.
inline static public function modsSounds(path:String, key:String) {
return modFolders(path + '/' + key + '.' + SOUND_EXT);
}
//Loads images in mods/images.
inline static public function modsImages(key:String) {
return modFolders('images/' + key + '.png');
}
//Loads xml files in mods/images.
inline static public function modsXml(key:String) {
return modFolders('images/' + key + '.xml');
}
//Loads txt files in mods/images.
inline static public function modsTxt(key:String) {
return modFolders('images/' + key + '.txt');
}
/* Goes unused for now
inline static public function modsShaderFragment(key:String, ?library:String)
{
return modFolders('shaders/'+key+'.frag');
}
inline static public function modsShaderVertex(key:String, ?library:String)
{
return modFolders('shaders/'+key+'.vert');
}
inline static public function modsAchievements(key:String) {
return modFolders('achievements/' + key + '.json');
}*/
static public function modFolders(key:String) {
if(currentModDirectory != null && currentModDirectory.length > 0) {
var fileToCheck:String = mods(currentModDirectory + '/' + key);
if(FileSystem.exists(fileToCheck)) {
return fileToCheck;
}
}
for(mod in getGlobalMods()){
var fileToCheck:String = mods(mod + '/' + key);
if(FileSystem.exists(fileToCheck))
return fileToCheck;
}
return SUtil.getPath() + 'mods/' + key;
}
public static var globalMods:Array<String> = [];
static public function getGlobalMods()
return globalMods;
static public function pushGlobalMods() // prob a better way to do this but idc
{
globalMods = [];
var path:String = SUtil.getPath() + 'modsList.txt';
if(FileSystem.exists(path))
{
var list:Array<String> = CoolUtil.coolTextFile(path);
for (i in list)
{
var dat = i.split("|");
if (dat[1] == "1")
{
var folder = dat[0];
var path = Paths.mods(folder + '/pack.json');
if(FileSystem.exists(path)) {
try{
var rawJson:String = File.getContent(path);
if(rawJson != null && rawJson.length > 0) {
var stuff:Dynamic = Json.parse(rawJson);
var global:Bool = Reflect.getProperty(stuff, "runsGlobally");
if(global)globalMods.push(dat[0]);
}
} catch(e:Dynamic){
trace(e);
}
}
}
}
}
return globalMods;
}
static public function getModDirectories():Array<String> {
var list:Array<String> = [];
var modsFolder:String = mods();
if(FileSystem.exists(modsFolder)) {
for (folder in FileSystem.readDirectory(modsFolder)) {
var path = haxe.io.Path.join([modsFolder, folder]);
if (sys.FileSystem.isDirectory(path) && !ignoreModFolders.contains(folder) && !list.contains(folder)) {
list.push(folder);
}
}
}
return list;
}
#end
}