2
0
Эх сурвалжийг харах

Datafiles: on file changed, only reload this file instead of fully reloading all data files

lviguier 1 жил өмнө
parent
commit
6a10091997

+ 464 - 9
hide/comp/cdb/DataFiles.hx

@@ -20,7 +20,7 @@ class DataFiles {
 
 	static var changed : Bool;
 	static var skip : Int = 0;
-	static var watching : Map<String, Bool> = new Map();
+	static var watching : Map<String, Void -> Void > = new Map();
 
 	#if (editor || cdb_datafiles)
 	static var base(get,never) : cdb.Database;
@@ -35,8 +35,394 @@ class DataFiles {
 				loadSheet(sheet);
 	}
 
+	public static function loadFile( file : String, sheet : cdb.Sheet) @:privateAccess {
+		var sheetName = getTypeName(sheet);
+		var levelID = file.split("/").pop().split(".").shift();
+		levelID = levelID.charAt(0).toUpperCase()+levelID.substr(1);
+
+		function loadRec( p : hrt.prefab.Prefab, parent : hrt.prefab.Prefab, toRemove : Array<DataProps> ) {
+			// Initiliaze to remove list with the full list of lines data.
+			if (parent == null) {
+				toRemove = new Array<DataProps>();
+				for (ld in sheet.sheet.linesData)
+					if (ld.file == file)
+						toRemove.push(ld);
+			}
+
+			if( p.getCdbType() == sheetName ) {
+				var dprops : DataProps = {
+					file : file,
+					path : p.getAbsPath(),
+					index : 0,
+					origin : haxe.Json.stringify(p.props)
+				};
+
+				if( parent != null ) {
+					for( c in parent.children ) {
+						if( c == p ) break;
+						if( c.name == p.name ) dprops.index++;
+					}
+				}
+
+				if( sheet.idCol != null && Reflect.field(p.props,sheet.idCol.name) == "" )
+					Reflect.setField(p.props,sheet.idCol.name,levelID+"_"+p.name+(dprops.index == 0 ? "" : ""+dprops.index));
+
+				var changed = false;
+				for (idx => ld in sheet.sheet.linesData) {
+					if (ld.file == file && ld.path == p.getAbsPath()) {
+						if (ld.index == dprops.index) {
+							toRemove.remove(sheet.sheet.linesData[idx]);
+							sheet.sheet.linesData[idx] = dprops;
+							sheet.sheet.lines[idx] = p.props;
+							changed = true;
+						}
+					}
+				}
+
+				// Meaning that this is new data to add so insert it at the right index
+				if (!changed) {
+					var sepIdx = getSeparatorForPath(file, sheet);
+					var idxInsert = sheet.sheet.separators[sepIdx].index;
+
+					// Insert new line
+					sheet.sheet.linesData.insert(idxInsert, dprops);
+					sheet.sheet.lines.insert(idxInsert, p.props);
+
+					// Shift separators
+					for (idx in (sepIdx + 1)...sheet.sheet.separators.length)
+						sheet.sheet.separators[idx].index++;
+				}
+			}
+
+			for( c in p )loadRec(c,p,toRemove);
+
+			if (parent == null) {
+				for (rem in toRemove) {
+					var idxRemove = sheet.sheet.linesData.indexOf(rem);
+
+					// Shift sperators
+					var sepIdx = sheet.sheet.separators.length;
+					for (idx => s in sheet.sheet.separators) {
+						if (s.index > idxRemove) {
+							sepIdx = idx;
+							break;
+						}
+
+					}
+
+					for (idx in (sepIdx)...sheet.sheet.separators.length)
+						sheet.sheet.separators[idx].index--;
+
+					sheet.sheet.linesData.remove(rem);
+					sheet.sheet.lines.remove(sheet.sheet.lines[idxRemove]);
+
+					// Remove potentials un-used separators
+					removeSeparatorForPath(file, sheet);
+				}
+			}
+		}
+
+		var p = loadPrefab(file);
+		loadRec(p,null,[]);
+	}
+
+	/*
+		Return the index of the corresponding separator in the sheet's separators array for a path.
+		If there's not already a separator for the path, create it.
+	*/
+	static function getSeparatorForPath(path: String, sheet: cdb.Sheet) : Int {
+		var separators = @:privateAccess sheet.sheet.separators;
+
+		function comparePath(p1 : String, p2 : String) : Int {
+			var p1Parts = p1.split("/");
+			var p2Parts = p2.split("/");
+
+			var idx = 0;
+			while (true) {
+				if (p1Parts.length <= idx || p2Parts.length <= idx || p1Parts[idx] != p2Parts[idx])
+					return idx - 1;
+
+				idx++;
+			}
+		}
+
+		function findParentSep(sepIdx : Int) : cdb.Data.Separator {
+			var idx = sepIdx - 1;
+			while (idx > 0) {
+				if (separators[idx].level < separators[sepIdx].level)
+					return separators[idx];
+
+				idx--;
+			}
+
+			return null;
+		}
+
+		function addChildSeparator(sep: cdb.Data.Separator, parentSepIdx: Int) : Int {
+			// We might want to add it following the alphabetic order and not at the last position
+			for (idx in (parentSepIdx + 1)...separators.length) {
+				var s = separators[idx];
+
+				if (s.level < sep.level) {
+					separators.insert(idx, sep);
+					return idx;
+				}
+			}
+
+			separators.push(sep);
+			return separators.length -1;
+		}
+
+		// Try to find the most matching separator for this path
+		var matchingSepData = { sepIdx: -1, level: -1 };
+		for (sIdx => s in separators) {
+			var level = comparePath(s.path, path);
+			if (level > matchingSepData.level) {
+				matchingSepData.sepIdx = sIdx;
+				matchingSepData.level = level;
+			}
+		}
+
+		// Meaning that there's already a separator for this path
+		if (matchingSepData.level == path.split("/").length - 1)
+			return matchingSepData.sepIdx;
+
+		// Meaning that there is one partial matching separator for this path
+		if (matchingSepData.level != -1) {
+			var pathSplit = path.split("/");
+			var matchingSep = separators[matchingSepData.sepIdx];
+			var matchingSepPathSplit = matchingSep.path.split("/");
+
+			// Check if the matching sep is fully matching
+			if (matchingSepPathSplit.length == matchingSepData.level + 1) {
+				var sep : cdb.Data.Separator = {};
+				sep.level = matchingSep.level + 1;
+
+				var newPath = [ for(idx in (matchingSepData.level + 1)...pathSplit.length) pathSplit[idx]].join("/");
+				sep.path = matchingSep.path + "/" + newPath;
+				sep.title = StringTools.replace(newPath, "/", " > ");
+				if (sep.title.split(".").length >= 2) {
+					var tmp = sep.title.split(".");
+					tmp.pop();
+					sep.title = tmp.join("");
+				}
+
+				var idx = addChildSeparator(sep, matchingSepData.sepIdx);
+				sep.index = idx == separators.length - 1 ? @:privateAccess sheet.sheet.lines.length : separators[idx + 1].index - 1;
+				return idx;
+			}
+			else {
+				// Otherwise split the matching part of the separator
+
+				// Modify the most matching separator in a fully matching separator (remove the diff part of it)
+				var parentSep = findParentSep(matchingSepData.sepIdx);
+				var newPath = [ for(idx in 0...matchingSepData.level + 1) matchingSepPathSplit[idx]].join("/");
+				matchingSep.path = newPath;
+				matchingSep.title = StringTools.replace(parentSep != null ? newPath.substr(parentSep.path.length + 1) : newPath, "/", " > ");
+
+				// Create a new separator for the existings lines that were under the one we splitted
+				newPath = [ for(idx in (matchingSepData.level + 1)...matchingSepPathSplit.length) matchingSepPathSplit[idx] ].join("/");
+				var sep : cdb.Data.Separator = {
+					title : StringTools.replace(newPath, "/", " > "),
+					index : matchingSep.index,
+					level : matchingSep.level + 1,
+					path : matchingSep.path + "/" + newPath
+				};
+
+				if (sep.title.split(".").length >= 2) {
+					var tmp = sep.title.split(".");
+					tmp.pop();
+					sep.title = tmp.join("");
+				}
+
+				separators.insert(matchingSepData.sepIdx + 1, sep);
+
+				// Shift levels of separators that were children of the one we splitted
+				for (idx in (matchingSepData.sepIdx + 2)...separators.length) {
+					var s = separators[idx];
+
+					if (s.level <= matchingSep.level)
+						break;
+
+					s.level++;
+				}
+
+				// Then create a new separator for the new path
+				newPath = [ for(idx in (matchingSepData.level + 1)...pathSplit.length) pathSplit[idx]].join("/");
+				var newSep : cdb.Data.Separator = {
+					title : StringTools.replace(newPath, "/", " > "),
+					level : matchingSep.level + 1,
+					path : matchingSep.path + "/" + newPath
+				};
+
+				if (newSep.title.split(".").length >= 2) {
+					var tmp = newSep.title.split(".");
+					tmp.pop();
+					newSep.title = tmp.join("");
+				}
+
+				var newSepIdx = addChildSeparator(newSep, matchingSepData.sepIdx);
+				newSep.index = newSepIdx == separators.length - 1 ? @:privateAccess sheet.sheet.lines.length : separators[newSepIdx + 1].index - 1;
+				return newSepIdx;
+			}
+		}
+
+		// Meaning that there is no matching separator at all and we need to create one
+		var newSep : cdb.Data.Separator = {
+			title : StringTools.replace(path, "/", " > "),
+			level : 0,
+			path : path
+		};
+
+		if (newSep.title.split(".").length >= 2) {
+			var tmp = newSep.title.split(".");
+			tmp.pop();
+			newSep.title = tmp.join("");
+		}
+
+		var newSepIdx = addChildSeparator(newSep, -1);
+		newSep.index = newSepIdx == separators.length - 1 ? @:privateAccess sheet.sheet.lines.length : separators[newSepIdx + 1].index - 1;
+		return newSepIdx;
+	}
+
+	/*
+		Remove all separators that aren't used after modification of a file at this path
+	*/
+	static function removeSeparatorForPath(path: String, sheet: cdb.Sheet, deleteWithContent : Bool = false) {
+		var separators = @:privateAccess sheet.sheet.separators;
+		var lines = @:privateAccess sheet.sheet.lines;
+		var linesData = @:privateAccess sheet.sheet.linesData;
+
+		function isSeparatorEmpty(sepIdx : Int) : Bool {
+			if (sepIdx == separators.length - 1)
+				return separators[sepIdx].index > lines.length - 1;
+
+			var next = null;
+
+			var idx = sepIdx + 1;
+			while (idx < separators.length) {
+				if (separators[idx].level <= separators[sepIdx].level) {
+					next = separators[idx];
+					break;
+				}
+
+				idx++;
+			}
+
+			if (next == null)
+				return separators[sepIdx].index > lines.length - 1;
+
+
+			return next.index <= separators[sepIdx].index;
+		}
+
+		function findParentSepIdx(sepIdx : Int) : Int {
+			var idx = sepIdx - 1;
+			while (idx > 0) {
+				if (separators[idx].level < separators[sepIdx].level)
+					return idx;
+
+				idx--;
+			}
+
+			return -1;
+		}
+
+		function findChildrenSepIdx(sepIdx : Int) : Array<Int> {
+			var children = [];
+
+			if (sepIdx < 0 || sepIdx >= separators.length)
+				return children;
+
+			for (idx in (sepIdx + 1)...separators.length) {
+				var s = separators[idx];
+
+				if (findParentSepIdx(idx) == sepIdx)
+					children.push(idx);
+			}
+
+			return children;
+		}
+
+		// Find current separator corresponding to this path
+		var currentSepIdx = -1;
+		for (sIdx => s in separators) {
+			if (s.path == path) {
+				currentSepIdx = sIdx;
+				break;
+			}
+		}
+
+		if (currentSepIdx == -1)
+			return;
+
+		// Delete content of the separator we want to remove (lines, separator etc)
+		if (deleteWithContent) {
+			var currentSep = separators[currentSepIdx];
+			var idx = currentSepIdx + 1;
+			var begin = currentSepIdx;
+			var end = currentSepIdx;
+			var lineBegin = separators[begin].index;
+			var lineEnd = separators[end].index + 1;
+			while (true) {
+				if (idx >= separators.length || separators[idx].level >= currentSep.level) {
+					lineEnd = idx >= separators.length ? lines.length : separators[idx].index;
+					break;
+				}
+
+				idx++;
+				end = idx;
+			}
+
+
+			var idx = end;
+			while (idx > begin) {
+				separators.remove(separators[idx]);
+				currentSepIdx--;
+				idx--;
+			}
+
+			var idx = lineEnd - 1;
+			while (idx >= lineBegin) {
+				lines.remove(lines[idx]);
+				linesData.remove(linesData[idx]);
+				idx--;
+			}
+
+			// Shift separators
+			var diff = lineEnd - lineBegin;
+			for (sepIdx in (begin+1)...separators.length) {
+					separators[sepIdx].index -= diff;
+			}
+		}
+
+		// Remove all empty separators (deletion is applied on parents too)
+		var parentSepIdx = currentSepIdx;
+		while (parentSepIdx != - 1) {
+			if (isSeparatorEmpty(parentSepIdx)) {
+				var tmp = findParentSepIdx(parentSepIdx);
+				separators.remove(separators[parentSepIdx]);
+				parentSepIdx = tmp;
+			}
+
+			break;
+		}
+
+		// Merge separator with parent if parent has exactly one child separator
+		var childrenSep = findChildrenSepIdx(parentSepIdx);
+		if (childrenSep.length == 1) {
+			var childSep = separators[childrenSep[0]];
+			var parentSep = separators[parentSepIdx];
+
+			parentSep.path = childSep.path;
+			parentSep.title = parentSep.title + " > " + childSep.title;
+
+			separators.remove(childSep);
+		}
+	}
+
 	#if (editor || cdb_datafiles)
-	static function onFileChanged() {
+	static function onFileChanged(path: String) {
 		if( skip > 0 ) {
 			skip--;
 			return;
@@ -45,16 +431,84 @@ class DataFiles {
 		haxe.Timer.delay(function() {
 			if( !changed ) return;
 			changed = false;
-			reload();
-			Editor.refreshAll(true);
+
+			// Only reload data files that are concerned by this file modification
+			function reloadFile(path: String) {
+				if( !watching.exists(path) ) {
+					var fun = () -> onFileChanged(path);
+					watching.set(path, fun);
+					Ide.inst.fileWatcher.register(path, fun, true);
+				}
+
+				var fullPath = Ide.inst.getPath(path);
+				if (sys.FileSystem.isDirectory(fullPath)) {
+					var files = sys.FileSystem.readDirectory(fullPath);
+					for (f in files)
+						reloadFile(path + "/" + f);
+
+					// If a file is deleted, this method is triggered with parent file, in that
+					// case we need to retrieve deleted file to remove it.
+					var deletedFiles : Array<String> = [];
+					for (p in DataFiles.watching.keys()) {
+						var abs = Ide.inst.getPath(p);
+
+						// Meaning this is a deleted file
+						if (StringTools.contains(abs, fullPath) && abs != fullPath && !sys.FileSystem.exists(abs)) {
+							Ide.inst.fileWatcher.unregister(p, watching.get(p));
+							watching.remove(p);
+
+							if (!sys.FileSystem.isDirectory(abs)) {
+								for( sheet in base.sheets ) {
+									if( sheet.props.dataFiles != null ) {
+										var dataFiles = sheet.props.dataFiles.split(";");
+										for (dataFile in dataFiles) {
+											var reg = new EReg("^"+dataFile.split(".").join("\\.").split("*").join(".*")+"$","");
+											if (reg.match(p))
+												DataFiles.removeSeparatorForPath(p, sheet, true);
+										}
+									}
+								}
+							}
+						}
+
+					}
+
+					return;
+				}
+
+				for( sheet in base.sheets ) {
+					if( sheet.props.dataFiles != null ) {
+						var dataFiles = sheet.props.dataFiles.split(";");
+						for (dataFile in dataFiles) {
+							var reg = new EReg("^"+dataFile.split(".").join("\\.").split("*").join(".*")+"$","");
+							if (reg.match(path))
+								DataFiles.loadFile(path, sheet);
+						}
+					}
+				}
+			}
+
+			// When deleting a file in hide (cf FileTree -> onDeleteFile()), each files are deleted
+			// one by one, and each might not trigger the onFileChanged method because of the delay.
+			// So we try to find the top level folder that still exists and reload it.
+			var f = path;
+			while (!sys.FileSystem.exists(Ide.inst.getPath(f)) && f != "") {
+				var arr = f.split("/");
+				arr.pop();
+				f = arr.join("/");
+			}
+
+			reloadFile(f);
+			Editor.refreshAll(true, false);
 		},0);
 	}
 
 	static function loadPrefab(file) {
 		var p = Ide.inst.loadPrefab(file);
 		if( !watching.exists(file) ) {
-			watching.set(file, true);
-			Ide.inst.fileWatcher.register(file, onFileChanged);
+			var fun = () -> onFileChanged(file);
+			watching.set(file, fun);
+			Ide.inst.fileWatcher.register(file, fun);
 		}
 		return p;
 	}
@@ -155,8 +609,9 @@ class DataFiles {
 					return;
 				#if (editor || cdb_datafiles)
 				if( !watching.exists(path) ) {
-					watching.set(path, true);
-					Ide.inst.fileWatcher.register(path, onFileChanged, true);
+					var fun = () -> onFileChanged(path);
+					watching.set(path, fun);
+					Ide.inst.fileWatcher.register(path, fun, true);
 				}
 				#end
 				var reg = new EReg("^"+part.split(".").join("\\.").split("*").join(".*")+"$","");
@@ -209,7 +664,7 @@ class DataFiles {
 		for( r in root.subs )
 			browseRec(r, 0);
 	}
-	
+
 	public static function getPrefabsByPath(prefab: hrt.prefab.Prefab, path : String ) : Array<hrt.prefab.Prefab> {
 		function rec(prefab: hrt.prefab.Prefab, parts : Array<String>, index : Int, out : Array<hrt.prefab.Prefab> ) {
 			var name = parts[index++];

+ 13 - 12
hide/comp/cdb/Editor.hx

@@ -1056,9 +1056,10 @@ class Editor extends Component {
 	}
 
 	public static var inRefreshAll(default,null) : Bool;
-	public static function refreshAll( eraseUndo = false ) {
+	public static function refreshAll( eraseUndo = false, loadDataFiles = true) {
 		var editors : Array<Editor> = [for( e in new Element(".is-cdb-editor").elements() ) e.data("cdb")];
-		DataFiles.load();
+		if (loadDataFiles)
+			DataFiles.load();
 		inRefreshAll = true;
 		for( e in editors ) {
 			e.syncSheet(Ide.inst.database);
@@ -1658,7 +1659,7 @@ class Editor extends Component {
 		var cats = ide.projectConfig.dbCategories;
 		return cats == null || props.categories == null || cats.filter(c -> props.categories.indexOf(c) >= 0).length > 0;
 	}
-	
+
 	public function moveColumn(targetSheet: cdb.Sheet, origSheet: cdb.Sheet, col: cdb.Data.Column) : String {
 		beginChanges(true);
 		var err = targetSheet.addColumn(col);
@@ -1671,8 +1672,8 @@ class Editor extends Component {
 			var subTarget = base.getSheet(subTargetPath);
 			if (subSheet != null) {
 				if (subTarget == null)
-					return 'original sheet $subSheetPath contains columns but target sheet $subTargetPath does not exist'; 
-				
+					return 'original sheet $subSheetPath contains columns but target sheet $subTargetPath does not exist';
+
 				for (c in subSheet.columns) {
 					var err = subTarget.addColumn(c);
 					if (err != null)
@@ -1694,18 +1695,18 @@ class Editor extends Component {
 						throw "missing parent table that is not props";
 					}
 					commonSheet = base.getSheet(commonPath.join("@"));
-	
+
 					if (!commonSheet.props.isProps)
 						break;
 					commonPath.pop();
 				}
-	
+
 				var origPath = origSheet.getPath().split("@");
 				origPath.splice(0, commonPath.length);
 				origPath.push(col.name);
 				var targetPath = targetSheet.getPath().split("@");
 				targetPath.splice(0, commonPath.length);
-	
+
 				var lines = commonSheet.getLines();
 				for (i => line in lines) {
 					// read value from origPath
@@ -1715,7 +1716,7 @@ class Editor extends Component {
 						if (value == null)
 							break;
 					}
-	
+
 					if (value != null) {
 						// Get or insert intermediates props value along targetPath
 						var target : Dynamic = line;
@@ -1730,7 +1731,7 @@ class Editor extends Component {
 						Reflect.setField(target, col.name, value);
 					}
 				}
-	
+
 				origSheet.deleteColumn(col.name);
 			}
 		}
@@ -1770,7 +1771,7 @@ class Editor extends Component {
 							}
 							cdbPath.pop();
 						}
-	
+
 						for (p in path) {
 							cdbPath.push(p);
 							var subSheet = base.getSheet(cdbPath.join("@"));
@@ -1781,7 +1782,7 @@ class Editor extends Component {
 								return 'Target path "${cdbPath.join(".")}" goes inside or outside another sheet';
 							}
 						}
-	
+
 						var finalPath = cdbPath.join("@");
 						var targetSheet = base.getSheet(finalPath);
 						if (targetSheet != null) {