digitalmars.D.learn - What it the preferred method to write a class to a file?
- Charles D Hixson (19/19) Jul 22 2006 I'm thinking that I should build two methods, possibly
- Stewart Gordon (37/56) Jul 23 2006 That won't work at all. Because classes have reference semantics, all
- Charles D Hixson (2/62) Jul 23 2006 Thanks. (Sigh...I guess this is all tied in with introspection.)
- Chad J (5/27) Jul 23 2006 There was a discussion about this a while ago that had some suggestions,...
- Charles D Hixson (46/60) Jul 23 2006 Well, before I read it I had written (slightly trimmed):
- Jarrett Billingsley (9/14) Jul 23 2006 You don't even need a routine to do it.
- Charles D Hixson (6/27) Jul 23 2006 O, dear. Yes, I see it.
- Regan Heath (6/33) Jul 23 2006 If it works, then I say put it in a function and ignore 'how' it works. ...
- Charles D Hixson (5/34) Jul 24 2006 Right you are. That's what I did...though it's a tiny bit
- Regan Heath (69/96) Jul 23 2006 Some alternatives to consider...
- Jarrett Billingsley (7/16) Jul 24 2006 I don't think a .dup is required here; when you write "*cast(uint*)str.p...
- Regan Heath (4/22) Jul 24 2006 Oops, you're right.
- xs0 (5/10) Jul 24 2006 union {
- Charles D Hixson (4/17) Jul 24 2006 For some reason when I tried that I was told that I was
- Derek (11/28) Jul 24 2006 How about
- Charles D Hixson (27/54) Jul 25 2006 characters. My current (working) versions are:
- BCS (16/38) Jul 25 2006 I would define a struct that contains all of the trivial types and
- Charles D Hixson (7/7) Jul 25 2006 Well, here's where I am now.
- kris (28/124) Jul 25 2006 just an FYI ~ Mango has supported this for years now:
- Charles D Hixson (4/145) Jul 25 2006 Nice! Thanks.
I'm thinking that I should build two methods, possibly called write and print where write would export a "binary" version of the class that is not intended to be human readable and print would export something with, e.g., numbers translated into strings. It appears as if the methods should be instance methods of the class. It looks as if write should be implemented something like: void write(Stream stream) { stream.writeExact(&this, this.sizeof); } Though probably in this version I'd want to include delimiters, a type id, and a length to facilitate writing the corresponding read routing (which would need to be a class method). It this the best approach? Also are there any suggestions as to a reasonable way to specify the type id, so that it would be the same from run to run? Should I keep track of the ids myself (manually)? If not, how would I know on an attempt to read which class the type id referred to?
Jul 22 2006
Charles D Hixson wrote:I'm thinking that I should build two methods, possibly called write and print where write would export a "binary" version of the class that is not intended to be human readable and print would export something with, e.g., numbers translated into strings. It appears as if the methods should be instance methods of the class. It looks as if write should be implemented something like: void write(Stream stream) { stream.writeExact(&this, this.sizeof); }That won't work at all. Because classes have reference semantics, all it'll do is write out the memory address of the object. Even so, the arrangement of members within a class isn't guaranteed: http://www.digitalmars.com/d/class.html "The D compiler is free to rearrange the order of fields in a class to optimally pack them in an implementation-defined manner. Consider the fields much like the local variables in a function - the compiler assigns some to registers and shuffles others around all to get the optimal stack frame layout. This frees the code designer to organize the fields in a manner that makes the code more readable rather than being forced to organize it according to machine optimization rules. Explicit control of field layout is provided by struct/union types, not classes." Moreover, every object includes a pointer to the vtable, which will screw things up when the program is run again and the data is read back in. Add to that any pointers, dynamic arrays or object references that your class may contain....Though probably in this version I'd want to include delimiters, a type id, and a length to facilitate writing the corresponding read routing (which would need to be a class method).You need to define a data format that includes all the information that is needed in order to reconstruct the object. Start with a type ID of your own devising (if there's any chance that more than one type fits the context), and write out each member. For primitive types, static arrays and structs that have no reference-semantics members, this is trivial. For dynamic arrays, write out the length followed by the contents. For object references within the class, follow this principle recursively. Be careful of any potential circularity.It this the best approach? Also are there any suggestions as to a reasonable way to specify the type id, so that it would be the same from run to run?Only the one you suggest next:Should I keep track of the ids myself (manually)?<snip> Yes. Stewart. -- -----BEGIN GEEK CODE BLOCK----- Version: 3.1 GCS/M d- s:- C++ a->--- UB P+ L E W++ N+++ o K- w++ O? M V? PS- PE- Y? PGP- t- 5? X? R b DI? D G e++++ h-- r-- !y ------END GEEK CODE BLOCK------ My e-mail is valid but not my primary mailbox. Please keep replies on the 'group where everyone may benefit.
Jul 23 2006
Stewart Gordon wrote:Charles D Hixson wrote:Thanks. (Sigh...I guess this is all tied in with introspection.)I'm thinking that I should build two methods, possibly called write and print where write would export a "binary" version of the class that is not intended to be human readable and print would export something with, e.g., numbers translated into strings. It appears as if the methods should be instance methods of the class. It looks as if write should be implemented something like: void write(Stream stream) { stream.writeExact(&this, this.sizeof); }That won't work at all. Because classes have reference semantics, all it'll do is write out the memory address of the object. Even so, the arrangement of members within a class isn't guaranteed: http://www.digitalmars.com/d/class.html "The D compiler is free to rearrange the order of fields in a class to optimally pack them in an implementation-defined manner. Consider the fields much like the local variables in a function - the compiler assigns some to registers and shuffles others around all to get the optimal stack frame layout. This frees the code designer to organize the fields in a manner that makes the code more readable rather than being forced to organize it according to machine optimization rules. Explicit control of field layout is provided by struct/union types, not classes." Moreover, every object includes a pointer to the vtable, which will screw things up when the program is run again and the data is read back in. Add to that any pointers, dynamic arrays or object references that your class may contain....Though probably in this version I'd want to include delimiters, a type id, and a length to facilitate writing the corresponding read routing (which would need to be a class method).You need to define a data format that includes all the information that is needed in order to reconstruct the object. Start with a type ID of your own devising (if there's any chance that more than one type fits the context), and write out each member. For primitive types, static arrays and structs that have no reference-semantics members, this is trivial. For dynamic arrays, write out the length followed by the contents. For object references within the class, follow this principle recursively. Be careful of any potential circularity.It this the best approach? Also are there any suggestions as to a reasonable way to specify the type id, so that it would be the same from run to run?Only the one you suggest next:Should I keep track of the ids myself (manually)?<snip> Yes. Stewart.
Jul 23 2006
Charles D Hixson wrote:I'm thinking that I should build two methods, possibly called write and print where write would export a "binary" version of the class that is not intended to be human readable and print would export something with, e.g., numbers translated into strings. It appears as if the methods should be instance methods of the class. It looks as if write should be implemented something like: void write(Stream stream) { stream.writeExact(&this, this.sizeof); } Though probably in this version I'd want to include delimiters, a type id, and a length to facilitate writing the corresponding read routing (which would need to be a class method). It this the best approach? Also are there any suggestions as to a reasonable way to specify the type id, so that it would be the same from run to run? Should I keep track of the ids myself (manually)? If not, how would I know on an attempt to read which class the type id referred to?There was a discussion about this a while ago that had some suggestions, at least for the binary approach: http://www.digitalmars.com/d/archives/digitalmars/D/37739.html Hope it helps.
Jul 23 2006
Chad J wrote:Charles D Hixson wrote:Well, before I read it I had written (slightly trimmed): const uint eor = 0x19191919; class nnv { protected float[] vec; const char[4] sig = "nvec"; ulong write(Stream s) in { assert (s.isOpen()); assert (s.seekable); } body { ulong oStart = s.position; s.write(cast(ulong)0); s.write(sig[0]); s.write(sig[1]); s.write(sig[2]); s.write(sig[3]); s.write(cast(ulong)(this.vec.length)); foreach(float f; this.vec) s.write(f); s.write(eor); ulong oEnd = s.position; s.position = oStart; s.write(cast(ulong)(oEnd - oStart) ); s.position = oEnd; } } So I guess that we're heading in the same directions (except that for this class a separate struct didn't make much sense). I should probably calculate the overhead so that I don't need to go back and forth to write the length. I return the position to make it easy to create an index, and include length, type, and eor to make it feasible to rebuild an index if the current one becomes corrupt. (Theoretically I shouldn't need the eor...but I grew up with tape parity errors, and anyway any compression method would reduce that. The problems with this solution come in scaling. Consider what would happen if one were dealing with many different kinds of record...and some were composite. Do-able doesn't mean elegant. This basic design is limited in the number of kinds of record it can handle...but long before that limit is reached it would be paralyzed by the clumsiness. P.S.: Is there a standard library routine for converting between strings of length 4 and uint-s? If so I wasn't able to find it. If not, I wasn't able to determine that it didn't exist. (That would have made writing the sig more efficient.)...class method). It this the best approach? Also are there any suggestions as to a reasonable way to specify the type id, so that it would be the same from run to run? Should I keep track of the ids myself (manually)? If not, how would I know on an attempt to read which class the type id referred to?There was a discussion about this a while ago that had some suggestions, at least for the binary approach: http://www.digitalmars.com/d/archives/digitalmars/D/37739.html Hope it helps.
Jul 23 2006
"Charles D Hixson" <charleshixsn earthlink.net> wrote in message news:ea0rll$5h9$1 digitaldaemon.com...P.S.: Is there a standard library routine for converting between strings of length 4 and uint-s? If so I wasn't able to find it. If not, I wasn't able to determine that it didn't exist. (That would have made writing the sig more efficient.)You don't even need a routine to do it. char[] sig = "help"; uint s = *cast(uint*)sig.ptr; And the other way.. uint s = 0xAABBCCDD; char[] sig = new char[4]; *cast(uint*)sig.ptr = s;
Jul 23 2006
Jarrett Billingsley wrote:"Charles D Hixson" <charleshixsn earthlink.net> wrote in message news:ea0rll$5h9$1 digitaldaemon.com...O, dear. Yes, I see it. But one of the things that cause me to prefer D over C is the ability to avoid pointer manipulation, which to me seem extremely hazardous and, when one gets beyond simple cases, quite confusing. (And unmaintainable.)P.S.: Is there a standard library routine for converting between strings of length 4 and uint-s? If so I wasn't able to find it. If not, I wasn't able to determine that it didn't exist. (That would have made writing the sig more efficient.)You don't even need a routine to do it. char[] sig = "help"; uint s = *cast(uint*)sig.ptr; And the other way.. uint s = 0xAABBCCDD; char[] sig = new char[4]; *cast(uint*)sig.ptr = s;
Jul 23 2006
On Sun, 23 Jul 2006 17:26:57 -0700, Charles D Hixson <charleshixsn earthlink.net> wrote:Jarrett Billingsley wrote:If it works, then I say put it in a function and ignore 'how' it works. More than likely it will be inlined and you'll never need to worry about it again. Regan"Charles D Hixson" <charleshixsn earthlink.net> wrote in message news:ea0rll$5h9$1 digitaldaemon.com...O, dear. Yes, I see it. But one of the things that cause me to prefer D over C is the ability to avoid pointer manipulation, which to me seem extremely hazardous and, when one gets beyond simple cases, quite confusing. (And unmaintainable.)P.S.: Is there a standard library routine for converting between strings of length 4 and uint-s? If so I wasn't able to find it. If not, I wasn't able to determine that it didn't exist. (That would have made writing the sig more efficient.)You don't even need a routine to do it. char[] sig = "help"; uint s = *cast(uint*)sig.ptr; And the other way.. uint s = 0xAABBCCDD; char[] sig = new char[4]; *cast(uint*)sig.ptr = s;
Jul 23 2006
Regan Heath wrote:On Sun, 23 Jul 2006 17:26:57 -0700, Charles D Hixson <charleshixsn earthlink.net> wrote:Right you are. That's what I did...though it's a tiny bit ugly because D won't allow functions to return char[4] and won't allow them as out parameters. (So I had to make it a char[], and that worked.)Jarrett Billingsley wrote:If it works, then I say put it in a function and ignore 'how' it works. More than likely it will be inlined and you'll never need to worry about it again. Regan"Charles D Hixson" <charleshixsn earthlink.net> wrote in messageO, dear. Yes, I see it. But one of the things that cause me to prefer D over C is the ability to avoid pointer manipulation, which to me seem extremely hazardous and, when one gets beyond simple cases, quite confusing. (And unmaintainable.)...You don't even need a routine to do it. char[] sig = "help"; uint s = *cast(uint*)sig.ptr; And the other way.. uint s = 0xAABBCCDD; char[] sig = new char[4]; *cast(uint*)sig.ptr = s;
Jul 24 2006
On Sun, 23 Jul 2006 17:26:57 -0700, Charles D Hixson <charleshixsn earthlink.net> wrote:Jarrett Billingsley wrote:Some alternatives to consider... import std.stdio; uint char_to_uint(char[] str) { return *cast(uint*)str.ptr; } uint char_to_uint_a(char[] str) { uint i = 0; for(int j = 3; j >= 0; j--) { i <<= 8; i |= str[j]; } return i; } /* Note: .dup is required the local 'i' becomes invalid after the function returns char[] uint_to_char(uint i) { char[] str = new char[4]; *cast(uint*)str.ptr = i; return str.dup; } Note: no dup required, we're copying the data to the new array. char[] uint_to_char(uint i) { char[] str = new char[4]; str[] = (cast(char*)&i)[0..4]; return str; } */ char[] uint_to_char(uint i) { return (cast(char*)&i)[0..4].dup; } char[] uint_to_char_a(uint i) { char[] str = new char[4]; foreach(inout c; str) { c = i&0xFF; i >>= 8; } return str; } void main() { char[] str = "abcd"; writefln("%b (%d)",char_to_uint(str),char_to_uint(str)); writefln("%b (%d)",char_to_uint_a(str),char_to_uint_a(str)); writefln(uint_to_char(char_to_uint(str))); writefln(uint_to_char_a(char_to_uint(str))); } Also, have you considered the 'endian' issues of storing data in a binary format. See: http://en.wikipedia.org/wiki/Endian Specifically, if you plan to use a file saved on a machine which is little endian, i.e. your typical x86 pentium/amd and transfer it to a big endian machine i.e. a solaris sparc server or powerPC and load it. If you do then you will need to decide on an endian format to save to and load from and therefore perform conversion on one of the systems (and not the other). To perform conversion you would simply modify the _a functions above to process the characters in reverse order. Regan"Charles D Hixson" <charleshixsn earthlink.net> wrote in message news:ea0rll$5h9$1 digitaldaemon.com...O, dear. Yes, I see it. But one of the things that cause me to prefer D over C is the ability to avoid pointer manipulation, which to me seem extremely hazardous and, when one gets beyond simple cases, quite confusing. (And unmaintainable.)P.S.: Is there a standard library routine for converting between strings of length 4 and uint-s? If so I wasn't able to find it. If not, I wasn't able to determine that it didn't exist. (That would have made writing the sig more efficient.)You don't even need a routine to do it. char[] sig = "help"; uint s = *cast(uint*)sig.ptr; And the other way.. uint s = 0xAABBCCDD; char[] sig = new char[4]; *cast(uint*)sig.ptr = s;
Jul 23 2006
"Regan Heath" <regan netwin.co.nz> wrote in message news:optc56yyzv23k2f5 nrage...Note: .dup is required the local 'i' becomes invalid after the function returns char[] uint_to_char(uint i) { char[] str = new char[4]; *cast(uint*)str.ptr = i; return str.dup; }I don't think a .dup is required here; when you write "*cast(uint*)str.ptr = i", it's copying the data from i to the array's memory, not setting the array's pointer to point to the integer. Option 2 is actually semantically the same. Would be kind of interesting to see what machine code each version produces :)
Jul 24 2006
On Mon, 24 Jul 2006 20:15:03 -0400, Jarrett Billingsley <kb3ctd2 yahoo.com> wrote:"Regan Heath" <regan netwin.co.nz> wrote in message news:optc56yyzv23k2f5 nrage...Oops, you're right.Note: .dup is required the local 'i' becomes invalid after the function returns char[] uint_to_char(uint i) { char[] str = new char[4]; *cast(uint*)str.ptr = i; return str.dup; }I don't think a .dup is required here; when you write "*cast(uint*)str.ptr = i", it's copying the data from i to the array's memory, not setting the array's pointer to point to the integer.Option 2 is actually semantically the same. Would be kind of interesting to see what machine code each version produces :)Regan
Jul 24 2006
P.S.: Is there a standard library routine for converting between strings of length 4 and uint-s? If so I wasn't able to find it. If not, I wasn't able to determine that it didn't exist. (That would have made writing the sig more efficient.)union { uint asUInt; char[4] asChars; } or something to that effect :)
Jul 24 2006
xs0 wrote:For some reason when I tried that I was told that I was using "illegal utf8 characters". But Jarrett's approach worked fine.P.S.: Is there a standard library routine for converting between strings of length 4 and uint-s? If so I wasn't able to find it. If not, I wasn't able to determine that it didn't exist. (That would have made writing the sig more efficient.)union { uint asUInt; char[4] asChars; } or something to that effect :)
Jul 24 2006
On Mon, 24 Jul 2006 13:34:12 -0700, Charles D Hixson wrote:xs0 wrote:How about union { uint asUInt; ubyte[4] asChars; } Are you really using *characters* or *bytes*? -- Derek Parnell Melbourne, Australia "Down with mediocrity!"For some reason when I tried that I was told that I was using "illegal utf8 characters". But Jarrett's approach worked fine.P.S.: Is there a standard library routine for converting between strings of length 4 and uint-s? If so I wasn't able to find it. If not, I wasn't able to determine that it didn't exist. (That would have made writing the sig more efficient.)union { uint asUInt; char[4] asChars; } or something to that effect :)
Jul 24 2006
Derek wrote:On Mon, 24 Jul 2006 13:34:12 -0700, Charles D Hixson wrote:characters. My current (working) versions are: uint char4ToUint(char[4] item) { return *cast(uint *)item.ptr; } char[] uintToChar4(uint item) { char[] itmStr = new char[4]; *cast(uint *)itmStr.ptr = item; return itmStr[0..4]; } Note that even with the version the "illegal utf8 characters" sneaks in unless I return a range from the string. I have no idea as to why, I've just tried various things until something worked. (Note the heavy influence of Jarrett's code.) The test I used was to run a string through both of them in turn, and check that the output was the same as the input. I didn't check all possible strings, so there may be corner cases. E.g., there may well be numbers that will generate illegal utf-8 characters. (I can't see how, but then I don't know why this version works and several that appeared to me to be essentially equivalent didn't work.) I didn't save the version using a union, and I didn't try a byte array. (I don't know why...perhaps that seemed like a way of dodging the error message without dodging the error? Since currently a lot of what is happening feels like magic, I tend to be a bit conservative when an unexplained error message shows up.)xs0 wrote:How about union { uint asUInt; ubyte[4] asChars; } Are you really using *characters* or *bytes*?For some reason when I tried that I was told that I was using "illegal utf8 characters". But Jarrett's approach worked fine.P.S.: Is there a standard library routine for converting between strings of length 4 and uint-s? If so I wasn't able to find it. If not, I wasn't able to determine that it didn't exist. (That would have made writing the sig more efficient.)union { uint asUInt; char[4] asChars; } or something to that effect :)
Jul 25 2006
Charles D Hixson wrote:I'm thinking that I should build two methods, possibly called write and print where write would export a "binary" version of the class that is not intended to be human readable and print would export something with, e.g., numbers translated into strings. It appears as if the methods should be instance methods of the class. It looks as if write should be implemented something like: void write(Stream stream) { stream.writeExact(&this, this.sizeof); } Though probably in this version I'd want to include delimiters, a type id, and a length to facilitate writing the corresponding read routing (which would need to be a class method). It this the best approach? Also are there any suggestions as to a reasonable way to specify the type id, so that it would be the same from run to run? Should I keep track of the ids myself (manually)? If not, how would I know on an attempt to read which class the type id referred to?I would define a struct that contains all of the trivial types and placeholders for the non trivial types (arrays would be replaced by an Array struct, etc.) In the simple case all that is needed is to copy the elements into the struct and output it. If reference types are used, more complicated things need to be done. One option is to block out space for the struct and then output the referenced data (storing file pointers to the data in the struct), then backup and put the struct in the hole you left. Referenced objects might still be an issue but they may be handled recursively (watch out for circular references). Pulling data out of the file is much the same. I haven't tried anything like this with covariant classes. So I haven't any ideas on how to make that work. This has the advantage over sterilization that the file can be navigated like the original data without having to walk the entire file.
Jul 25 2006
Well, here's where I am now. Granted the get and save aren't totally general...but it demonstrates where I'm headed. I plan to save several different instances of different classes to the same file. (And, of course, to keep an index...which I'll need to write just as I close the file...or possibly to keep in a separate file.)
Jul 25 2006
just an FYI ~ Mango has supported this for years now: class Vec : IReadable, IWritable { float[] vec; char[] sig; void read (IReader input) { input (sig) (vec); } void write (IWriter output) { output (sig) (vec); } } void main() { auto vec = new Vec; auto f = new FileConduit ("myfile", FileConduit.ReadWriteCreate); auto write = new Writer (f); auto read = new Reader (f); // write and flush write (vec) (); // rewind and read f.seek (0); read (vec); } Reader/Writer has endian versions, and text versions ... Charles D Hixson wrote:Well, here's where I am now. Granted the get and save aren't totally general...but it demonstrates where I'm headed. I plan to save several different instances of different classes to the same file. (And, of course, to keep an index...which I'll need to write just as I close the file...or possibly to keep in a separate file.) ------------------------------------------------------------------------ class Vec { protected float[] vec; const char[4] sig = "nVec"; // constructors this(int len, float val) { initRand(); vec.length = len; for (int i = 0; i < vec.length; i++) vec[i] = val; } // a lot of stuff was removed here /** * Export the object to a seekable stream. The format is only * intended for computers, but it should be lossless. * The returned value is the stream address of the start of the object. */ ulong write(Stream s) in { assert (s.isOpen()); assert (s.writeable); assert (s.seekable); } body { ulong oStart = s.position; s.write(cast(uint)0); s.write(char4ToUint(sig)); s.write(cast(uint)(this.vec.length)); foreach(float f; this.vec) s.write(f); s.write(eor); ulong oEnd = s.position; s.position(oStart); s.write(cast(uint)(oEnd - oStart) ); s.position(oEnd); return oStart; } /** * Import the object from a seekable stream. The format is only * intended for computers, but it should be lossless. */ static Vec get(Stream s) in { assert (s.isOpen()); assert (s.readable); assert (s.seekable); } body { ulong oStart = s.position; Vec v = new Vec(); uint len, vlen, veor, tmpSig2; char[] sig2; s.read(len); s.read(tmpSig2); sig2 = uintToChar4(tmpSig2); if (sig != sig2) { s.position(oStart); writefln("original signature = <", sig, ">"); writefln("read in signature = <", sig2, ">"); throw new Exception ("Vec:get: Signature mismatch"); } s.read(cast(uint)(vlen)); v.length = vlen; for(int i = 0; i < vlen; i++) s.read(v.vec[i]); s.read(veor); if (eor != veor) { s.position(oStart); throw new Exception ("Vec:get: eor not detected when expected"); } ulong oEnd = s.position; if (len != cast(uint)(oEnd - oStart) ) { s.position(oStart); writefln("length calculated = %d", len); writefln("length read = %d", cast(uint)(oEnd - oStart)); throw new Exception ("Vec:get: length calculated does not match length read"); } return v; } // get }
Jul 25 2006
kris wrote:just an FYI ~ Mango has supported this for years now: class Vec : IReadable, IWritable { float[] vec; char[] sig; void read (IReader input) { input (sig) (vec); } void write (IWriter output) { output (sig) (vec); } } void main() { auto vec = new Vec; auto f = new FileConduit ("myfile", FileConduit.ReadWriteCreate); auto write = new Writer (f); auto read = new Reader (f); // write and flush write (vec) (); // rewind and read f.seek (0); read (vec); } Reader/Writer has endian versions, and text versions ... Charles D Hixson wrote:Nice! Thanks. I'll need to look into what else Mango provides...it may save me a lot of work.Well, here's where I am now. Granted the get and save aren't totally general...but it demonstrates where I'm headed. I plan to save several different instances of different classes to the same file. (And, of course, to keep an index...which I'll need to write just as I close the file...or possibly to keep in a separate file.) ------------------------------------------------------------------------ class Vec { protected float[] vec; const char[4] sig = "nVec"; // constructors this(int len, float val) { initRand(); vec.length = len; for (int i = 0; i < vec.length; i++) vec[i] = val; } // a lot of stuff was removed here /** * Export the object to a seekable stream. The format is only * intended for computers, but it should be lossless. * The returned value is the stream address of the start of the object. */ ulong write(Stream s) in { assert (s.isOpen()); assert (s.writeable); assert (s.seekable); } body { ulong oStart = s.position; s.write(cast(uint)0); s.write(char4ToUint(sig)); s.write(cast(uint)(this.vec.length)); foreach(float f; this.vec) s.write(f); s.write(eor); ulong oEnd = s.position; s.position(oStart); s.write(cast(uint)(oEnd - oStart) ); s.position(oEnd); return oStart; } /** * Import the object from a seekable stream. The format is only * intended for computers, but it should be lossless. */ static Vec get(Stream s) in { assert (s.isOpen()); assert (s.readable); assert (s.seekable); } body { ulong oStart = s.position; Vec v = new Vec(); uint len, vlen, veor, tmpSig2; char[] sig2; s.read(len); s.read(tmpSig2); sig2 = uintToChar4(tmpSig2); if (sig != sig2) { s.position(oStart); writefln("original signature = <", sig, ">"); writefln("read in signature = <", sig2, ">"); throw new Exception ("Vec:get: Signature mismatch"); } s.read(cast(uint)(vlen)); v.length = vlen; for(int i = 0; i < vlen; i++) s.read(v.vec[i]); s.read(veor); if (eor != veor) { s.position(oStart); throw new Exception ("Vec:get: eor not detected when expected"); } ulong oEnd = s.position; if (len != cast(uint)(oEnd - oStart) ) { s.position(oStart); writefln("length calculated = %d", len); writefln("length read = %d", cast(uint)(oEnd - oStart)); throw new Exception ("Vec:get: length calculated does not match length read"); } return v; } // get }
Jul 25 2006