Queuing up operations to run as a batch (example)

cy via Digitalmars-d digitalmars-d at puremagic.com
Mon Aug 22 21:17:37 PDT 2016


aka I finally found a use for mixin templates :p

When I can batch up operations and run them quicker all at once, 
I usually make a list of those operations and append to it in 
lieu of calling the operation. Then periodically flushing the 
list, and actually calling the operation. Database insertions, or 
disk writes, or network sends, or unit tests, or something like 
that. Lazily caching operations to all fire at once, basically. 
So, I wrote a thing to do that in D, and it actually works pretty 
well. Thought I'd share it.

Basically it works like this:

struct Foo {
   void direct_operation(int a, int b) { ... }
   void transaction(Callable)(Callable inside) {
     setup();
     scope(exit) takedown();
     inside();
   }
   mixin template Batch!(transaction,direct_operation) operation;
}

...

Foo foo;
foo.operation(1,2);
foo.operation(3,4);
foo.operation(5,6);
foo.operation.flush();
foo.operation(7,8);

And here's the actual code:

mixin template Batch(alias transaction, alias operation, size_t 
max = 0x10) {
	import std.typecons: Tuple, tuple;
	import std.traits: Parameters,ReturnType;
	import std.array: Appender;
	
	static assert(is(ReturnType!operation == void));
	alias Item = Tuple!(Parameters!operation);
	Appender!(Item[]) cache;
	void opCall(Parameters!operation args) {
		cache.put(tuple(args));
		if(cache.data.length > max)
			flush();
	}
	void flush() {
		if(cache.data.length == 0) return;
		scope(exit) cache.shrinkTo(0);
		transaction({
				foreach(ref args; cache.data) {
					operation(args.expand);
				}
			});
	}
	void completely(Handle)(Handle handle) {
		scope(exit) flush();
		handle();
	}
}

mixin template Batch(alias setup, alias operation, alias 
takedown, size_t max = 0x10) {
	void transaction(Callable)(Callable inside) {
		setup();
		scope(exit) takedown();
		inside();
	}
	mixin Batch!(transaction,operation,max);
}


unittest {
	import print: print;
	struct Foo {
		void batchable_operation(int a, int b) {
			print("operate on",a,b);
		}
		void setup() {
			print("setup for flushing");
		}
		void commit() {
			print("commit");
		}
		void opCall(int a, int b) {
			print("oops",a,b);
		}
		mixin Batch!(setup,
								 batchable_operation,
								 commit,
								 0x10) C;
	}

	Foo foo;

	foo.completely({
			for(int i=0;i<20;++i) {
				foo.C(i,i*2);
				// .C isn't needed... except when the struct implements the
				// same operation, in which case that's the default!
				foo(i,i*3);
				print("did we do it?",i);
			}
		});
	print("done");

	struct Bar {
		void transaction(Callable)(Callable inside) {
			print("beginning");
			scope(exit) print("ending");
			inside();
		}
		void batchable_operation(int a, int b) {
			print("bar on",a,b);
		}
		mixin Batch!(transaction,
								 batchable_operation) C;
	}

	Bar bar;
	bar.completely({
			for(int i=0;i<20;++i) {
				bar(i,i*2);
			}
		});
}


More information about the Digitalmars-d mailing list