Saving a file using Future


#1

Hi,

I was wondering if I’m using Future the right way to save a file. Thank you for your feedback!

private function _export_levels():Void {
		//I need this ENTER_FRAME to see where the export is at,
		//because it's doing the export in one long frame
		Lib.current.stage.addEventListener(Event.ENTER_FRAME, update_export);
		
		var create_export_file = new Future(function(){
			//
			//Creating a big String that will go into a JSON file
			//
			
			//Not sure what to return...
			//Anything seems to work to tell the function that I'm done
			return true;
			
		}, true);
		
		create_export_file.onComplete(function(future) {
			Lib.current.stage.removeEventListener(Event.ENTER_FRAME, update_export);
			
			//A second "Future" to save a temp file for safety
			var save_file = new Future(function() {
				File.saveContent(path + "temp", levels_to_export);
				
				return true;
			}, true);
			
			save_file.onComplete(function(future) {
				//Deleting the old file
				if (FileSystem.exists(path + file_name) == true) {
					FileSystem.deleteFile(path + file_name);
				}
				
				//Renaming the temp file to the actual save file
				FileSystem.rename(path + "temp", path + file_name);
			});
		});
	}

Thank you for all the help everyone!


#2

Although you can use Future to do some quick background work, you may end up with multi-threading issues when writing/reading your progress. It’s not really intended for use in a case where you need to send progress.

The BackgroundWorker API is more robust, and probably is closer to what you want?

var worker = new BackgroundWorker ();
worker.onProgress.add (function (progress:Float) {
    trace (progress);
});
worker.onComplete.add (function (path:String) {
    trace ("Wrote file: " + path);
});
worker.doWork.add (function (state:Dynamic) {
    worker.sendProgress (1 / 3);
    // do work
    worker.sendProgress (2 / 3);
    /// do work
    worker.sendProgress (3 / 3);
    worker.sendComplete (state.path);
});
worker.run ({ path: "file.out", data: ... });

The values you send into the worker, to progress, or to the completion handler are all dynamic, so you can decide what type of value you will use.

Perhaps we should make worker.run return a Future, it may be a good idea for consistency.

If you do use Future, and don’t need progress, you can chain them, which makes them easier to manage:

public function processData (data:Dynamic):Future<String> {
    return new Future (function () {
        // process data
        return jsonString;
    });
}

public function saveFile (path:String):Future<String> {
    return new Future (function () {
        // save file
        return path;
    }, true);
}

public function cleanup (path:String):Future<String> {
    return new Future (function () {
        // cleanup
        return path;
    }, true);
}

processData ({ data: 100 }).then (saveFile).then (cleanup)
    .onComplete (function (path:String) {
        trace ("finished processing file to " + path);
    }).onError (function (e:Dynamic) {
        trace (e);
    });

If you want to use futures and get more advanced in how they are used, create a Promise, which holds a Future. The key is that the Future object is read-only, but the Promise object allows you to dispatch progress, or conclude with an error or completion condition in the future. The cool thing about chaining futures is that an error will go all the way down to the end, and that you can pass one object type to the next, so you might start with a String and end with a ByteArray, or whatever makes sense. If you ever have something that does not need to be done asynchronously, you can also use the static Future.withValue and Future.withError methods.


#3

Thank you for your reply!

I think I might use those two solutions together.

The reason I was using Future is that I’m creating my String that is going to be saved in the json file in a single frame using a for loop and it freezes the app for a couple of second. While the app is frozen by the for loop, the other thread keeps track of where the loop is. Do you think it’s a good approach in this case? Or maybe I can use worker.sendProgress inside my for loop?

For the saving a temp file part and then renaming it, I think using BackgroundWorker is a better option.


#4

Yeah, I think you could worker.sendProgress each iteration (or every few iterations) to post progress. If you don’t use something like BackgroundWorker, you’ll run into multi-threaded access issues, but that should handle it automatically for you :slight_smile:

You could also combine multiple behaviors in one background task, depending on how you want to do it, and presume that the saving is some percentage of your total progress :slight_smile:


#5

Cool, thanks! I’ll try that when I have the time.

Again, you’re a great help! :heart: