D: C++ Streamoperator

Ich hatte gestern mal wieder mit C++ zu tun und muss sagen: ich liebe dessen Streamoperatoren. Gut bei der Ausgabe mittels cout nervt es mich schon hin und wieder, aber das einlesen von Konsoleneingaben mittels cin und vor allem bei Dateien ist es wirklich bequem und meiner Meinung nach nicht schwer zu verstehen. Vielleicht etwas kryptisch auf den ersten Blick, zugegeben, aber das legt sich bald wenn man sich erstmal daran gewöhnt hat. ;)
Für die zukünftige Version von Dgame habe ich mir die Streamoperationen von C++ emuliert, indem ich den Shift Operator überladen habe.
Damit habe ich mir eine Input/Output sowie File Stream Emulierung gebastelt:

Input & Output:

Show »

import std.stdio;
import std.conv : to;
 
immutable(string) endl = "\n\r";
 
/**
* Beispiel:
*
* ---
* Output cout = new Output();
* Input cin   = new Input();
*
* string name;
* ubyte age;
* 
* cout << "Hallo Welt!" << endl;
* cout << "Wie lautet dein Name?" << endl;
* cin >> name;
* cout << "Und wie alt bist du?" << endl;
* cin >> age;
* 
* writefln("My name is %s. I'm %d years old!", name, age);
* 
* cout << "Das ist also " << name << ". Nice to meet you." << endl;
* ---
*/
 
/**
* Autor: Randy Schütt
*/
 
class Output {
public:
	void output(T)(T param) const {
		write(param);
	}
 
	typeof(this) opBinary(string op, T)(T param) if (op == "<<") {
		this.output(param);
 
		return this;
	}
}
 
class Input {
public:
	void input(T)(ref T param) const {
		string value = readln();
 
		try {
			param = to!(T)(value[0 .. $ - 1]);
		} catch (Exception e) {
			param = cast(T)(null);
		}
	}
 
	typeof(this) opBinary(string op, T)(ref T param) if (op == ">>") {
		this.input(param);
 
		return this;
	}
}

Und File Stream:

Show »

private import std.stdio;
private import std.file;
private import std.regex;
 
/**
* Beispiel:
*
* ---
* File f = File.open("test.txt");
* 
* foreach (line; f) {
* 	  writeln(" > ", line);
* }
*
* writeln("Erste Zeile: ");
* writeln(f.readline());
* writeln("Zweite Zeile: ");
* writeln(f.readline());
*
* string third_line;
* f >>> third_line;
* writeln("Dritte Zeile: ");
* writeln(third_line);
*
* string content;
* f >> content;
*
* writeln("Content: ");
* writeln(content);
*
* f << "// Abschlusszeile";
* ---
*/
 
interface Iterator {
	void next();
	void previous();
}
 
class File : public Iterator {
private:
	const string _filename;
 
	string[] _lines;
	uint 	  _current_line;
	bool 	  _close;
 
public:
	alias readlines this;
 
	static typeof(this) open(string filename) {
		if (!File.exists(filename)) {
			throw new Exception("File not found: " ~ filename);
		}
 
		return new this(filename);
	}
 
	static bool exists(string filename) {
		return std.file.exists(filename);
	}
 
	static void put_content(string filename, string content) {
		std.file.write(filename, content);
	}
 
	static string get_content(string filename) {
		return cast(string) std.file.read(filename);
	}
 
	this(string filename) {
		if (!File.exists(filename)) {
			throw new Exception("File not found: " ~ filename);
		}
 
		this._filename = filename;
		this._current_line = 0;
 
		this.open();
		this.refresh();
	}
 
	void open() {
		this._close = false;
	}
 
	bool closed() const {
		return this._close;
	}
 
	void close() {
		this._close = true;
	}
 
	string read() const {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		return File.get_content(this._filename);
	}
 
	void write(string content) {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		std.file.append(this._filename, content ~ "\r\n");
 
		this.refresh();
	}
 
	void refresh() {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		this._lines = [];
 
		string line;
 
		auto f = std.stdio.File(this._filename, "r");
		while (!f.eof()) {
			line = f.readln();
 
			this._lines ~= line;
		}
		f.close();
	}
 
	const(string)[] readlines() const {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		return this._lines;
	}
 
	string readline(int line = -1) {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		if (line >= 0) {
			this._current_line = line;
		}
 
		return this._lines[this._current_line++];
	}
 
	void next() {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		this._current_line++;
 
		if (this._current_line >= this._lines.length) {
			this._current_line = this._lines.length;
		}
	}
 
	void previous() {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		if (this._current_line > 0) {
			this._current_line--;
		}
	}
 
	uint current_line() const {
		return this._current_line;
	}
 
	string get_filename() const {
		return this._filename;
	}
 
	/*
	bool opBinary(string op, T)(T param, out int line) {
		switch (op) {
			case "in":
				foreach (string line; this) {
					auto result = std.regex.match(regex(r"" ~ param), line, "g");
 
					if (!result.empty()) {
						return true;
					}
				}
			break;
 
			default: break;
		}
 
		return false;
	}
	*/
 
	typeof(this) opBinary(string op, T)(T value) if (op == "<<") {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		this.write(value);
 
		return this;
	}
 
	typeof(this) opBinary(string op, T)(ref T value) if (op == ">>") {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		value = this.read();
 
		return this;
	}
 
	typeof(this) opBinary(string op, T)(ref T value) if (op == ">>>") {
		if (this._close) {
			throw new Exception("File is closed!");
		}
 
		value = this.readline();
 
		return this;
	}
}

Vielleicht hat ja jemand Interesse und/oder Spaß daran. ;)

Referenz oder doch Zeiger?

In C oder C++ würde man stets einen Zeiger übergeben, sofern man in der zu übergebenen Methode/Funktion diesem Parameter einen neuen Wert zuweisen würde. Denn ansonsten würde dieser neu zugewiesene Wert am Ende der Methode/Funktion wieder verfallen.
Es ist nicht einmal so, dass man das tun würde, sondern das man es tun muss, da man in C/C++ keine andere Wahl hat.
In D und auch in C# hat man dafür das Keyword “ref”, wobei man in D dennoch noch Zeiger benutzen kann.
Bei einem Benchmark, welches denn nun schneller sei, stellte ich fest, dass kaum merkbare Unterschiede festzustellen sind und ref als Syntax wirklich angenehmer wirkt.
Doch wofür dann noch, außer natürlich für C Kompatibilität, in D Zeiger benutzen?
More »

D: mutable Keyword

Einige kennen sicher das Keyword mutable aus C++.
In D gibt es kein entsprechendes Äquivalent, const bleibt auch const. Diese Gebenheit nervte mich in letzter Zeit jedoch etwas. Vor allen bei debugging/Log Sachen wäre es mal nützlich, einfach eine mutable Eigenschaft in einer als const Deklarierten Methode zu verwenden.

Für alle die es nicht wissen: eine const deklarierte Methode sieht folgendermaßen aus:

  1. void foo() const {
  2. //some code here
  3. }

Das const “sagt” dem Compiler sozusagen, dass diese Methode das Objekt an sich nicht verändert.

Allerdings: wenn eine Eigenschaft eines Objekts verändert wird, ändert sich doch der komplette Zustand des Objekts. Also wozu als mutable deklarieren?
Debugging oder Logging Mitschnitte (oder derartig vergleichbares) verändern nicht wirklich den Zustand, jedenfalls nicht ausschlaggebend. Und für Debugging immer wieder das const auszukommentieren und in der Release wieder hinzuzufügen kann schon nach einer kurzen Weile etwas “nerven”.

Daher habe ich mir eine kleine Struktur geschrieben und einen dazugehörigen Test der die Benutzung demonstriert.
Damit sollte das mutable aus C++ emuliert werden. Eventuell hat ja jemand Interesse daran:

Show »

private import std.stdio;
private import std.conv;
 
final struct mutable(T) {
private:
	const T _value;
 
	void _assign(U)(U val) const {
		//writeln("private assign: " ~ to!(string)(val));
		T * copy = cast(T*) &this._value;
		*copy = cast(T) val;
	}
 
public:
	this(T val) {
		this._value = val;
	}
 
	this(const ref mutable!(T) val) {
		this._value = val.value;
	}
 
	mutable!(T) opAssign(U)(U val) const 
        if (std.traits.isIntegral!(U) 
        || std.traits.isSomeString!(U) 
        || is(U == enum)) {
		this._assign(cast(T) val);
 
		return this;
	}
 
	mutable!(T) opAssign(U)(/+const ref +/mutable!(U) val) const {
		this._assign(val.value);
 
		return this;
	}
 
	mutable!(T) opOpAssign(string op, U)(U val) const {
		switch (op) {
			case "+":
				writeln("ADD");
				U res = this._value + val;
				this._assign(res);
			break;
 
			case "-":
				writeln("SUB");
				U res = this._value - val;
				this._assign(res);
			break;
 
			case "*":
				writeln("MUL");
				U res = this._value * val;
				this._assign(res);
			break;
 
			case "/":
				writeln("DIV");
				U res = this._value / val;
				this._assign(res);
			break;
 
			default: break;
		}
 
		return this;
	}
 
	mutable!(U) opBinary(string op, U)(U val) const {
		//mutable!(U) m;
		switch (op) {
			case "+":
				writeln("Bin. ADD");
				U res = this._value + val;
 
				//m = res;
				return mutable!(U)(res);
			//break;
 
			case "-":
				writeln("Bin. SUB");
				U res = this._value - val;
 
				//m = res;+
				return mutable!(U)(res);
			//break;
 
			case "*":
				writeln("Bin. MUL");
				U res = this._value * val;
 
				//writeln(typeid(U));
 
				//m = res;
				return mutable!(U)(res);
			//break;
 
			case "/":
				writeln("Bin. DIV");
				U res = this._value / val;
 
				//m = res;
				return mutable!(U)(res);
			//break;
 
			default: break;
		}
 
		//return m;
		assert(0);
	}
 
	bool opEquals(U)(const ref mutable!(U) m) const {
		if (m is null) return false;
		if (m is this) return true;
 
		return m.value == this._value;
	}
 
	bool opEquals(U)(U m) const {
		return m == this._value;
	}
 
	@property
	T value() const {
		writeln("VALUE: ", this._value);
		return this._value;
	}
 
	string toString() const {
		return to!(string)(this._value);	
	}
}
 
enum Status {
    Stopped,
    Paused,
    Playing
}
 
class B {
private:
	mutable!(ubyte)  _id;
	mutable!(Status) _status;
 
public:			
	void pause() const {
		this._status = Status.Paused;
	}
 
	void stop() const {
		this._status = Status.Stopped;
	}
 
	void play() const {
		this._status = Status.Playing;
	}
 
	Status get_status() const {
		return this._status.value;
	}
 
	void setup(const ubyte val) const {
		_id = val;
		writefln("(=) ID: %d", _id);
 
		_id = _id.value + 2;
		writefln("(+) ID: %d", _id);
 
		_id += 2;
		writefln("(+=) ID: %d", _id);
 
		_id -= 32;
		writefln("(-=) ID: %d", _id);
 
		_id *= 2;
		writefln("(*=) ID: %d", _id);
 
		_id = _id + 2;
		writefln("(!+!) ID: %d", _id);
 
		_id = _id - 32;
		writefln("(!-!) ID: %d", _id);
 
		_id = _id * 1.2;
		writefln("(!*!) ID: %d", _id);
		writeln(typeid(typeof(_id.value)));
 
		_id = _id / 2.0;
		writefln("(!/!) ID: %d", _id);
		writeln(typeid(typeof(_id.value)));
	}
}
 
// in der main:
B b = new B();
b.setup(128);
 
writeln(b.get_status());
b.play();
writeln(b.get_status());
b.pause();
writeln(b.get_status());
b.play();
writeln(b.get_status());
b.stop();
writeln(b.get_status());
b.play();
writeln(b.get_status());

Nachtrag vom 12.01.2012

Ich habe die obige Struktur nochmal überarbeitet und an den Feinheiten geschliffen. Zudem habe ich nun daraus eine Klasse gemacht (geschah eher nach belieben als aus einem konkretem Grund).
Hier ist der Code:

Show »

private import std.stdio;
private import std.conv : to;
 
static private import std.traits;
 
final class Mutable(T) {
private:
	const T _value;
 
	void _assign(U)(const U val) const {
		T * copy = cast(T*) &this._value;
		*copy = cast(T) val;
	}
 
public:
	this(const T value) {
		this._value = value;
	}
 
	this(const ref T value) {
		this._value = value;
	}
 
	void set_value(U)(U val) {
		this._assign(val);
	}
 
	@property
	T value() const {
		return cast(T) this._value;
	}
 
	alias value this;
 
	@disable
	static Mutable!(T) opAssign(U)(U val) {
		return null;//new Mutable!(T)(val);
	}
 
	const(Mutable!(T)) opAssign(U)(U val) const {
	    if (is(T == Mutable)) {
            throw new Exception("Can't assign Mutable to Mutable!");
	    }
 
		this._assign(val);
 
		return this;
	}
 
	const(Mutable!(T)) opOpAssign(string op, U)(U val) const {
		switch (op) {
			case "+":
				//writeln("ADD");
				U result = this._value + val;
 
				this._assign(result);
			break;
 
			case "-":
				//writeln("SUB");
				U result = this._value - val;
 
				this._assign(result);
			break;
 
			case "*":
				//writeln("MUL");
				U result = this._value * val;
 
				this._assign(result);
			break;
 
			case "/":
				//writeln("DIV");
				U result = this._value / val;
 
				this._assign(result);
			break;
 
			default: break;
		}
 
		return this;
	}
 
	Mutable!(U) opBinary(string op, U)(U val) const {
		switch (op) {
			case "+":
				U result = this._value + val;
 
				return new Mutable!(U)(result);
 
			case "-":
				U result = this._value - val;
 
				return new Mutable!(U)(result);
 
			case "*":
				U result = this._value * val;
 
				return new Mutable!(U)(result);
 
			case "/":
				U result = this._value / val;
 
				return new Mutable!(U)(result);
 
			default: break;
		}
 
		assert(0);
	}
 
	bool opEquals(const T cmp) const {
		return cmp == this._value;
	}
 
	bool opEquals(const Mutable!(T) cmp) const {
		return cmp.value == this._value;
	}
 
	override string toString() const {
		return to!(string)(this._value);
	}
}
 
 
enum Status {
    Stopped,
    Paused,
    Playing
}
 
class B {
private:
	const Mutable!(ubyte)  _id; // mit "const" wird sichergestellt, dass _id beim ersten Aufruf initialisiert wird.
	const Mutable!(Status) _status;
 
public:
	this() {
		this._id = new Mutable!(ubyte)(0);
		this._status = new Mutable!(Status)(Status.Stopped);
	}
 
	void pause() const {
		this._status = Status.Paused;
	}
 
	void stop() const {
		this._status = Status.Stopped;
	}
 
	void play() const {
		this._status = Status.Playing;
	}
 
	Status get_status() const {
		return this._status.value;
	}
 
	void setup(const ubyte val) const {
		_id = val;
		writefln("(=) ID: %d", _id);
 
		_id = _id.value + 2;
		writefln("(+) ID: %d", _id);
 
		_id += 2;
		writefln("(+=) ID: %d", _id);
 
		_id -= 32;
		writefln("(-=) ID: %d", _id);
 
		_id *= 2;
		writefln("(*=) ID: %d", _id);
 
		_id = _id + 2;
		writefln("(!+!) ID: %d", _id);
 
		_id = _id - 32;
		writefln("(!-!) ID: %d", _id);
 
		_id = _id * 1.2;
		writefln("(!*!) ID: %d", _id);
		writeln(typeid(typeof(_id.value)));
		writeln(typeid(typeof(_id)));
 
		assert(_id == 204);
 
		if (_id == 204) {
			writeln("Korrekt");
		}
 
		_id = _id / 2.0;
		writefln("(!/!) ID: %d", _id);
		writeln(typeid(typeof(_id.value)));
		writeln(typeid(typeof(_id)));
 
		assert(_id == 102);
	}
}
 
 
void main() {
	B b = new B();
	b.setup(128);
 
	writeln(b.get_status());
	b.play();
	writeln(b.get_status());
	b.pause();
	writeln(b.get_status());
	b.play();
	writeln(b.get_status());
	b.stop();
	writeln(b.get_status());
	b.play();
	writeln(b.get_status());
}

Vertex Buffer Objects (VBO)

Da sich die Frage aus dem letzten Artikel geklärt hat (ja, OpenGL ist SDL wahrlich aus vielerlei Gründen vorzuziehen) möchte ich nun mein neu erwonnenes Wissen teilen.

In diesem Artikel möchte ich mich mit Vertex Buffer Objects, kurz VBO, beschäftigen.
Viele werden den folgenden Sachverhalt kennen oder zumindest in der Theorie nachvollziehen können.
Angenommen wir haben eine TileMap. Den sichtbaren Ausschnitt der Karte (View) werden wir mit sehr hoher Wahrscheinlichkeit nicht in jedem Frame ändern, sondern er wird mehrere Frames überstehen. Je nach Kartengröße eventuell sogar die gesamte Spielzeit, da gar kein Scrolling nötig ist.
Versteht sich von selbst: ist die gesamte Karte kleiner oder gleich groß wie das Fenster -> kein Scrollen notwendig, da alles bereits zu sehen ist.
Allerdings zeichnet man üblicherweise in jedem Frame wieder und wieder die gesamte Karte. Und das braucht bei größeren Karten eine Menge Rechenlast.
Man kann zwar das ganze etwas abmildern, in dem man von vorn herein nicht sichtbare Teile der Karte nicht zeichnet, aber die sichtbaren Tiles/Teile der Karte müssen dennoch immer und immer wieder über den Bus in den Grafikspeicher.
More »

OpenGL oder SDL?

Die Frage stellt sich mir mehr und mehr.
Ich arbeite derzeit in den letzten Zügen an Dgame 1.6 und möchte danach direkt mit den Arbeiten an Version 1.7 beginnen.
Dort soll die große Änderung der Umstieg von SDL zu OpenGL sein, wobei nach neueren Kenntnisstand eher eine Symbiose zustande käme.
So würde man mittels der SDL das eigentliche Fenster erstellen und würde mit SDL_image weiterhin Bilder laden, diese dann aber, bzw. ihre Pixelsturktur, in eine GL Textur laden. Für etwaige Bildverarbeitung würden dann SDL spezifische Funktionen zu Hilfe genommen werden, wie es jetzt bereits der Fall ist.
OpenGL bzw. dessen Texturen würden für schnelleres und performanteres blitten und etwaige andere Bild Verarbeitungs Maßnahmen zu Hilfe genommen werden, wie etwa Rotation und Zoom.
More »

Copyright © All Rights Reserved · Green Hope Theme by Sivan & schiy · Proudly powered by WordPress