Register to post in forums, or Log in to your existing account
 

Post new topic  Reply to topic     Home » Forums » Zugg's Blog Goto page 1, 2  Next
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Fri Sep 01, 2006 11:14 pm   

CMUD 1.06 Status Blog
 
I'm making good progress on 1.06. The new task tracking program that I got is really helping me organize and prioritize my work.

I've already fixed the down-arrow crash in the command line, and have also fixed the problems editing/creating string lists in the settings editor, along with a few other misc crash bugs.

Today I re-coded the entire class used for Session icons in the main character screen. The old code (ported from zMUD) used a plain Delphi "Record" instead of a Class. And there were several places in the code where I was assigning this record:

MUDWindow.Session := CharSelection.Session

This used to work fine back in the old days of fixed-length strings, but in later versions of Delphi where they added dynamic strings and reference counting, this simple record assignment doesn't work if the record contains strings (which it does).

I have no idea why the FastMM memory manager doesn't flag these string problems. What happens is that Delphi assigns the string *pointer* instead of making a new reference to the existing string object. So now you have two pointers to the same string, but only one reference count. Seems like this should cause problems...maybe some of the obscure crashes were caused by this.

In any case, this isn't the proper way to code in Delphi anymore. Rather than having a simple record with fields it's much cleaner to create a class with properties. Especially in this case where it's a wrapper around a database record. It makes little sense to copy fields from the database into a record rather than just accessing the database directly and only storing the ID value for the database record.

Changing all of the record fields into class properties took a while. But the code is now *much* cleaner and should make it much easier to finish the work on multiple sessions and fix the problems with different windows sharing the same session record.

That's the big job for tomorrow: go through and test all of the different cases I can think of for multiple sessions, windows, etc and get all of that finally working.

The goal for 1.06 is to have all of the session/window/etc stuff working well, and to fix as many of the other reported bugs that I can. I'm hoping to have 1.06 ready by the end of next week.
Reply with quote
Seb
Wizard


Joined: 14 Aug 2004
Posts: 1269

PostPosted: Fri Sep 01, 2006 11:54 pm   Re: CMUD 1.06 Status Blog
 
Zugg wrote:
MUDWindow.Session := CharSelection.Session

This used to work fine back in the old days of fixed-length strings, but in later versions of Delphi where they added dynamic strings and reference counting, this simple record assignment doesn't work if the record contains strings (which it does).

What version of Delphi did they add that (if you know)? My boss at work uses Delphi 5 and he has a program that basically gets garbage in variables after a while, presumably due to invalid pointers. I've been encouraging him to use FastMM after your blog entry on that, but he was initially resistant. He might have started using it today though - not sure as he was working from home - I think I did persuade him to give it a try.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Sat Sep 02, 2006 5:14 pm   
 
First, *definitely* convince him to get and use FastMM. I have found *zero* problems with the latest version. It makes your program faster, and it will find all sorts of memory leaks and problems. I have found that no matter how much I want to pretend something reported by FastMM isn't really a problem, I've learned that if FastMM reports it as a problem, then *it is*! And I've fixed some pretty obscure problems in this way. Even with Delphi 5. I would never program in Delphi without FastMM now.

Avoid replacing the Delphi IDE memory manager DLL with FastMM. Just use FastMM in your own code (just add FastMM as the first entry in the Uses clause for the project source). And copy the Full_Debug DLL file into your project directory so that you get more detailed memory leak log files. But you don't need to distribute any additional DLL files with your application.

As far as the string changes, I think they were added in Delphi 3 or Delphi 5. It's been so long that I don't remember. But the potential problem definitely exists in Delphi 5 and later. Basically, if you use "ShortString" or "String[nnn]" in your declaration, then you are getting the old fixed-length strings that are implemented as arrays. These "short strings" are limited to a length of 255 characters, but they can be safely used in a record and will be copied correctly.

If you are just using the "String" datatype, then you are getting a dynamic string. These strings can have an unlimited length. They are implemented as referenced-counted pointers, similar to COM objects. Most of the details are hidden behind the scenes. With dynamic strings, you cannot use String[0] to get or set the length...you need to use Length(String) and SetLength(String,NewLength) to control the length.

With "short strings" you will waste a lot of time and memory. Consider two strings:
Code:
Var Short1, Short2: ShortString;
  Long1, Long2: String;
begin
  Short1 := 'this is a short string';
  Short2 := Short1;
  Long1 := 'this is a long string';
  Long2 := Long1;
end;

With the short strings, Delphi is actually allocating memory for two full strings, and when you assign Short1 to Short2, it is doing a full memory copy of the contents of Short1 into Short2. With the "long" strings, Delphi allocates the memory dynamically. When you assign Long1 to Long2, Delphi doesn't actually copy any data...it simply assigns the same pointer to Long2 and increments the reference count for the string. Only if you modify Long2 will it create a new copy of the string because it can detect that more than one string references the same memory location. So it makes a copy and assigns a new pointer to Long2 when you modify it.

If you put strings into a record:
Code:
Type TestRec = Record
  Short1: ShortString;
  Short2: String[80];
  Long1: String;
end;

var Rec1, Rec2: TestRec;
begin
  Rec1.Short1 := 'short test';
  Rec1.Short2 := 'short test 2';
  Rec1.Long1 := 'long test';
  Rec2 := Rec1;
end;

When Delphi copies Rec1 to Rec2 it is only copying the data bytes from one record to another. The short strings are stored "inline" in the record. For example, the Short2 string takes up 81 bytes (80 data bytes plus the length byte). However, the Long1 string only takes up 4 bytes for the pointer, so with Rec2 you have a copy of the pointer, but the reference count for the string hasn't been incremented.

So now, if you try to assign a value to Rec2:
Code:
Rec2.Long1 := 'new long';

Delphi thinks the string reference count is only 1, so it doesn't make a new copy of the string data. Instead, it changes the existing string data, which is still pointed to by Rec1.Long1. So, even though you think you have only changed the value of Rec2.Long1, if you print out Rec1.Long1 you will find that it also has the value of "new long" instead of the original "long test".

This kind of bug can drive you crazy and is really hard to track down. You've got two different string variables pointing to the same data. Even worse, if one of these is a local variable or a variable within a class, then when this variable goes "out of scope" and Delphi frees up the memory for it, now the other string will be pointing to unallocated memory.

FastMM will detect this last case if you try to access the unallocated memory. But if both strings exist in the same scope, then you never access unallocated memory and you don't get any memory errors. You'll just get wierd behavior because you have two strings pointing to the same memory.

Hope that makes sense. You should only get problems like this if you still use Records and assign them directly, or if you are doing low-level memory transfers using the Move function.

The proper way to handle this is to create a Class instead of a Record and implement an Assign method that copies each field from one class instance to another, and then use Assign to copy class instances rather than simple assignment. You'll notice that Delphi already does this with things like Fonts, Images, etc. Most all Delphi classes have some sort of Assign method.

Sorry for going off topic like that. But hey, this *is* a blog entry and not a normal forum post Wink Hope this helps your boss.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Sun Sep 03, 2006 1:53 am   
 
OK, back to the CMUD 1.06 topic.

First, I have to vent a bit. I'm really getting tired of people using CMUD who are not willing to read the forum posts and who don't understand what it means to TEST a BETA product. It seems like no matter how many disclaimers I force people to read when they Download and Install, there are some people who refuse to read it and then start complaining about something not working which is a known issue, or get bent out of shape because something isn't working.

We had this problem back with earlier versions of zMUD too. Back them I started restricting access to the beta forum. Might soon be time to do the same thing for CMUD and set up a membership list for the CMUD beta forum and Download page.

In any case, I'm starting to get annoyed and am going to start locking more posts that don't comply with the beta testing rules.

But back to the programming. Can you tell I'm in a bad mood? Yeah, it was an annoying programming day. I seemed to spend all day on mostly minor issues in the character session screen. I don't know where all of these bugs came from...a lot of this stuff used to work fine. But I was fixing issues with changing the icon for a session, creating folders, dragging sessions to folders, deleting sessions, copy/pasting sessions, etc. None of these functions were working very well. And most of the bugs had nothing to do with the changes I have made in the past few weeks.

In any case, I think I have gotten the session screen beat into submission. Icons are fixed, folders are fixed, copy/paste is fixed, etc. So now it will be Monday before I can start fixing the bugs in multiple-session stuff. I have a long list of stuff to test, and I'm sure it will take me a couple of days to get it all working. Especially at the rate I'm finding other bugs to fix.

The really good news is that I have also implemented drag/drop session icons to your desktop. When you drag a session icon and drop it onto the Windows desktop, a shortcut icon is created that can be used to run CMUD and launch that session.

Drag/Drop to the desktop turned out to be pretty complex (involving a lot of Windows COM objects). But I found a set of Delphi DragDrop components that implemented most of the hard work. Of course, as soon as I started using it, FastMM started detecting some memory leaks. Once again, I wish *ALL* Delphi developers would use FastMM to find their own memory bugs.

In this particular case, the author of DragDrop was doing a really bad thing: He had two units with "Finalization" code (called when the application terminates) that depends upon a specific order. In old versions of Delphi, the order that the finalization code was executed was pretty well defined. But in later versions of Delphi (I think it was in Delphi 5 or 6) Borland did something to make the order of finalization execution undefined. Even just recompiling an app can change the order.

So, I had to add some ExitProc tricks to force it to clean stuff up in the correct order. Once that was done, and a couple of other memory leaks, then it seems to work fine.

Of course, because of the session bugs, clicking on these shortcut icons doesn't work yet. CMUD currently seems to open 2 windows for each session. But that's on the list to test and fix on Monday. It *is* pretty nice to be able to drag the session icons to the desktop though :) And actually, you can drag them anywhere you want. You can drag them to QuickBar, or the Start/Programs, or Windows Explorer, or anything else that accepts *.LNK shortcut files.
Reply with quote
Seb
Wizard


Joined: 14 Aug 2004
Posts: 1269

PostPosted: Sun Sep 03, 2006 2:49 am   
 
Zugg wrote:
This kind of bug can drive you crazy and is really hard to track down. You've got two different string variables pointing to the same data. Even worse, if one of these is a local variable or a variable within a class, then when this variable goes "out of scope" and Delphi frees up the memory for it, now the other string will be pointing to unallocated memory.

...

Hope this helps your boss.

I have a feeling this may be what is happening, although I've not looked at this code, I do have an idea of how it functions generally. Thanks very much for all the information - I hope it helps him too: I will transmit this information to him on Monday.
Reply with quote
Rainchild
Wizard


Joined: 10 Oct 2000
Posts: 1551
Location: Australia

PostPosted: Sun Sep 03, 2006 11:33 pm   
 
Quote:
And actually, you can drag them anywhere you want. You can drag them to QuickBar, or the Start/Programs, or Windows Explorer, or anything else that accepts *.LNK shortcut files.


Ooh! Can't wait to pin my MUD to the start menu - who needs character select to pop up when you can just launch directly into the MUD! Nice! Very Happy
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Tue Sep 05, 2006 12:26 am   
 
OK, I have totally lost it. I've had another nervous breakdown. Once again, it's Delphi's database system that has driven me crazy.

I had a table in an SQL database that looked like this:
Code:
CREATE TABLE mytable
  id INTEGER PRIMARY key
  value INTEGER

OK, the job is to insert a new column into this table. Since SQLite doesn't support the MODIFY TABLE SQL statement, we have to implement it by hand. The psuedo-code is:
Code:
Copy existing table into cache
DROP TABLE mytable
Create new table structure
Copy cache into new table

Using the memDataset, which is an in-memory dataset, this is easy using the CopyFromDataSet method. It copies a source Dataset into a destination Dataset. So, the Delphi code ends up looking like this:
Code:
Query.SQL := 'SELECT * FROM mytable';
Query.Open;  // reads existing table into query
Mem.CopyFromDataSet( Query); // copies query into MemTable
Query.Close;
SQLExecute( 'DROP TABLE mytable');
SQLExecute( 'CREATE TABLE mytable
  id INTEGER PRIMARY KEY
  newfield INTEGER
  value INTEGER');
Query.Open; // now the table should be empty
Mem.CopyToDataSet( Query); // copies MemTable into query
Query.Close;

(Note, this still isn't exact code, but has been simplified for discussion purposes)

OK, this pretty much works. But it leaves the "newfield" values uninitialized. What I wanted to do is initialize the value of the newfield to have the same value is the id field. So, instead of using the built-in CopyToDataSet, I need to copy the data myself:
Code:
Mem.First;
while not(Mem.Eof) do begin
  Query.Append;
  CopyDataRecord( Mem, Query); // copies current Mem record into current Query record
  Query['newfield'].Value := Mem['id'].Value; // copies ID field into NewField
  Query.Post;
  Mem.Next;
  end;

Guess what? It copies all of the existing data, but the NewField is STILL undefined! OK, so I test this:

Query['newfield'].Value := 123;

it still doesn't work! OK, let's try a different field:

Query['value'].value := 123;

This works fine! This is when I start going crazy. How could it work to assign a value to the "value" field, but not the "newfield" field?

Take a few hours to try and figure this out. It took me about 6 hours of pounding my head against the wall. I stripped all of this out of CMUD and make a test program. Same behavior in the test program. It just didn't make any sense!

...

OK, are you back? Do you have a huge headache too? Are you screaming at the computer like I am?

I don't know how I finally stumbled upon the solution. I noticed that the problem only occured with *new* fields added to the database. Turns out that the TConnection object that is actually assigned to the SQL Database was being cached in memory. Executing the DROP TABLE and CREATE TABLE commands were not updating this cache. Somehow it knew about the new field column (NewField) and wasn't giving any error when executing the Query['newfield'] := 123 line. But when it went to update the actual data in the disk file, it still had the old structure cached and wasn't writing out this field.

The solution (somewhat of a kludge in my opinion) is to Close the TConnection object, and then reopen it again. So we now have:
Code:
...
Mem.CopyFromDataSet( Query); // copies query into MemTable
Query.Close;
SQLExecute( 'DROP TABLE mytable');
SQLExecute( 'CREATE TABLE mytable
  id INTEGER PRIMARY KEY
  newfield INTEGER
  value INTEGER');
DBConnection.Close;  // removes old structure from cache
DBConnection.Open;  // reloads cache with new structure
Query.Open;
...

How obscure was that?

OK, so why was I even messing with this kind of database stuff? Well, I found a pretty nasty bug in the CMUD Preferences stuff. If you've looked at the database tables, you will see a table called "config" that stores the preferences for each window/module. The field "parent" is the ID of the window/module, and the "id" field is the particular preference being set.

However, the problem is that the "id" field was also the "primary key" for the database, which means it must be unique. So, if two different windows set a different value for the same preference, the database was only storing one of these pairs.

So, what I needed to do was make a new field called "prefid" that is a copy of the current "id" field (but not the key field for the table). This allows multiple records with the same "id" value, but different "parent" values. This allows different windows to have different preferences.

As CMUD reads an older *.PKG file, it needs to detect the old config table format and automatically update it to the new format. It copies the existing "id" field values into the new "prefid" field. That's where I ran into the cache bug.

It was bad enough to find such a basic bug in the preferences code. But then to get sidetracked for hours and hours by the stupid caching of the database system just drove me over the edge. I'm sick of stuff like that.
Reply with quote
Rainchild
Wizard


Joined: 10 Oct 2000
Posts: 1551
Location: Australia

PostPosted: Tue Sep 05, 2006 3:18 am   
 
I'm not sure how SQL Lite works, but the other way to approach the problem would be:
Code:
SELECT *, id AS newfield FROM mytable

so the query result set already has all the necessary columns, then you don't have to modify any properties manually?
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Tue Sep 05, 2006 4:45 am   
 
No, that doesn't really help. It might work to initially read in the old database, but once the extra field is added to the table, then it needs to function as a seperate column. Using that select statement provides the initial data, but when you attempt to write it back to a new database table where the "newfield" is a seperate column, the same caching problem arises.

The problem doesn't seem to be specific to SQLite. It seems to be an issue with the Zeos database components caching the table structure internally.

I could try it with MySQL to be sure, but honestly, I'm so fed up with this bug that I don't want to work on it anymore.
Reply with quote
Rainchild
Wizard


Joined: 10 Oct 2000
Posts: 1551
Location: Australia

PostPosted: Tue Sep 05, 2006 5:24 am   
 
Oh I get what you're saying - the issue is with the newly created table not realising it has that extra column, not getting data out of the old one. Sorry I was thinking about the good old INSERT INTO table2 SELECT some, stuff FROM table1 that you can do with SQL Server.

I guess at least you know how to update the database files now, which will no doubt come in handy when working on the mapper and db module down the track.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Tue Sep 05, 2006 6:32 am   
 
It's too bad that development on Zeos pretty much stopped. I'd rather use SQLite 3.x instead of 2.8. I think they added the MODIFY TABLE to 3.x. And with just SQL statements I could have easily added a table and then copied one field to a new field. Someday (in my copious spare time) maybe I'll take the API myself and see if I can get SQLite 3.x to work. Certainly would make the Database Module easier with the MODIFY TABLE command.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Tue Sep 05, 2006 6:35 am   
 
Hmm, I take that back. Seems that work has resumed on Zeoslib and there might be a CVS build of SQLite 3.x support. Definitely something I'll look into for the Database module.
Reply with quote
Seb
Wizard


Joined: 14 Aug 2004
Posts: 1269

PostPosted: Tue Sep 05, 2006 9:01 am   
 
Wouldn't it have been possible to change the primary key to be a compound of "id", "parent"? Certainly this is possible in MySQL and MS-SQL Server. [Edit: Ah, I get it - not on an existing table? If it were possible, then you wouldn't need prefid.]

Also does SQLite support either the "SELECT fields INTO newtable FROM oldtable" or "INSERT INTO newtable SELECT fields FROM oldtable", plus "DROP TABLE oldtable", plus "RENAME TABLE tbl_name TO new_tbl_name"? If so, you can do it all in SQLite.

Anyway, I'm sure you found out something that will be useful in the future.


Last edited by Seb on Tue Sep 05, 2006 9:10 am; edited 1 time in total
Reply with quote
Seb
Wizard


Joined: 14 Aug 2004
Posts: 1269

PostPosted: Tue Sep 05, 2006 9:04 am   
 
Also, I suppose this didn't work without closing and reopening the database connection?
Code:
SQLExecute( 'UPDATE mytable SET newfield = id');
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Tue Sep 05, 2006 4:24 pm   
 
Yep, that doesn't work either. It's the basic problem of bypassing the Delphi database stuff and issuing direct SQL calls, then getting the Delphi stuff to update itself. I didn't want to close the entire DBConnection database object since there were already open queries from the main "settings" table. But I found a way to move the conversion of the config table earlier in the process so that the "settings" query hasn't been opened yet. This allows me to open/close the database.

But this could also come back to bite me in the future if I need to modify the structure of the "settings" table.
Reply with quote
Seb
Wizard


Joined: 14 Aug 2004
Posts: 1269

PostPosted: Sat Sep 09, 2006 5:52 pm   
 
If the CVS version of Zeoslib doesn't support SQLite 3.x, there do seem to be some other components out there for Delphi.

Anyway, I think I'm speaking for quite a few people when I say that we're dying for some news!
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Sat Sep 09, 2006 9:29 pm   
 
Seb: sorry to keep you all in suspense :) zApp is totally designed around Zeoslib, as is the rest of CMUD now, so I'm not switching away from that. Zeoslib is the best multi-database component I've seen for Delphi, even without SQLite 3.x.

As far as the 1.06 status, I'm still working hard on it. I've gotten all of the session selection stuff fixed, and it properly handles multiple windows that are connected to different hosts. In the settings editor, the Window object has options for "No network connection", "Use main session connection", or "Override network connection". With the last option you can specify a different host and port for the window.

All of the stuff with creating the list of default packages is implemented and working. I still need to actually create the EnglishDirections and EnglishKeypad packages and test those, but all of the hooks for multiple default packages are done.

Today I've been working on the package tabs in the settings editor. The "Show Default packages" now hides/shows the entire list of packages in the default list mentioned above. I also added another package selection combobox to the toolbar that allows you to view a particular package without looking for it's tab. It also allows you to select a particular default package, even if the tab for that package is currently hidden. Also fixed a bunch of bugs related to the package tabs.

I still need to fix a problem with drag/dropping to a different package. The package number is actually encoded as the upper bits of all "ID" fields in the internal database. So moving settings to a different package requires lots of changes to various database fields. This is a bit tricky, but I should be able to get it working today.

I've also been going through many of the bugs in the bug list. I've fixed several parser issues with [] brackets, using % on the command line, using %time without arguments, using %1 within {}, + character in trigger patterns, more bugs with () in paths (like .(open door)), and problems with dots after numbers like 1.test.

Back to the session issues...each session icon properly saves and loads it's window layout now. I also fixed desktop icons and command line arguments so that they load the correct session without making duplicate windows. I still need to add something to allow it to remember which window had the keyboard focus the last time. Also, I fixed a bug with the preferences not inheriting properly.

I don't know if I already talked about the preference inheritance problem or not. If you look in the hidden Advanced pulldown menu in the Preferences screen, you will see that each window/module has it's own Preferences and they can inherit from other preferences. In 1.05, each preference object inherited from the previous object that was created, making a linked list of preferences. This allows a package to contain a module that changes your preferences. For example, you could have a package that sets a particular color scheme for all of the windows loaded after that package.

The problem arose when you have multiple windows/modules within a single package. The second window preferences would inherit from the first window preferences within that package. Imagine that the first window was your main MUD window, and the second window was your Tell window. At first, it sounded correct to have the Tell window inherit the preferences from the main MUD window. But this turned out to be a bad idea.

The preference that illustrated the problem was the "tick timer" preference. Imagine that the tick timer is enabled in the main MUD window. If the Tell window inherits these preferences, then the Tell window would also have the tick timer enabled. Or, if you changed a color in the main MUD window, the Tell window colors would change too.

The solution was to make sure that multiple windows in the same package all inherited from the last window of the previous package. So, the Main MUD window and the Tell window are independant, but both inherit from the previous package preferences. This allows a previous package to change preferences (like colors) and effect windows loaded after it, but prevents windows in the same package from being dependant upon each other.

Yeah, it was a bit complicated, but it's working much better now. Along with the database problem that I mentioned before that was preventing preferences from different windows from being saved, the window preferences are now working as expected with each window having independant preferences.

So, what's still left? I still need to test/fix the #SESSION command so that it works as expected. Then I'll add a New Session and Save Session command to the File menu, and add the dropdown list of cached sessions to the Session toolbar button. Then I need to fix Expression Triggers, and a few other bugs like the New Child button in the settings editor.

Sorry I stopped doing daily updates to this blog. It's a bit distracting to do forum stuff when I'm in the middle of programming, no news usually means that I'm hard at work programming and fixing lots of stuff. And there was *lots* of stuff that needed fixing in this version. My current guess is that you'll see the 1.06 release in the middle of next week.

But I'm not going to rush this release. I released 1.05 when I did because we went out of town for a week and I figured it would be better to let people identify as many bugs as possible while I was gone. But with the 1.06 release, I want to *finally* get something that is useable. Even if some features are still missing, I want 1.06 to be stable and to work as expected for the features that are already implemented.

The other job I plan to finish sometime next week is to have a set of articles/documentation on how packages, modules, windows, etc really work. Now that all of the design problems seem to be fixed (in 1.06), I think it's probably a good time to start working on this documentation for these important new features.

So, that's the latest!
Reply with quote
Seb
Wizard


Joined: 14 Aug 2004
Posts: 1269

PostPosted: Sun Sep 10, 2006 12:45 am   
 
Zugg wrote:
As far as the 1.06 status, I'm still working hard on it.

That's why I thought. But it's still nice to know there is progress. Smile

Zugg wrote:
Sorry I stopped doing daily updates to this blog. It's a bit distracting to do forum stuff when I'm in the middle of programming, no news usually means that I'm hard at work programming and fixing lots of stuff.

No problem. You need to work the way you want to work and not be affected by what we want. Wink

Zugg wrote:
But with the 1.06 release, I want to *finally* get something that is useable. Even if some features are still missing, I want 1.06 to be stable and to work as expected for the features that are already implemented.

Sounds good. And there is more incentive for us to find the more obscure bugs. I'm looking forward to it.

Zugg wrote:
The other job I plan to finish sometime next week is to have a set of articles/documentation on how packages, modules, windows, etc really work. Now that all of the design problems seem to be fixed (in 1.06), I think it's probably a good time to start working on this documentation for these important new features.

Good idea - I think this will be appreciated. With the quantity of forum posts on the subject now, it is pretty difficult to work out exactly what is going on, particular if one missed some of the posts.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Mon Sep 11, 2006 7:59 pm   
 
Wow, dragging settings to a different package turned out to be even more complicated than I thought.

First it needs to change the ID field to encode the new package into the upper bits. Then it needs to search through the other settings in the database to look for any setting that is a child of this one (like if we are moving a class folder).

I figured that was tough enough. But then I found another complexity...

The in-memory database keeps track of whether a record is inserted, modified, or deleted so that when the background save routine actually writes the changes to the SQL database, it can generate the correct INSERT, UPDATE, or DELETE SQL statement. But in the case where we are moving something to a new package, we actually need to DELETE it from the existing SQL file, and then INSERT it into the new package file. But modifying the ID field with the new package just sets the record as "modified".

So, I had to do some low-level tricks to convince the internal database that the settings record was "inserted" instead of "modified" (so it would get inserted into the new package database), and then I had to add a separate statement to force it to be deleted from the existing package database.

Seems to work now, although it takes longer than just dragging stuff to a different class folder.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Tue Sep 12, 2006 11:14 pm   
 
1.06 is getting closer. I finished with all of the settings editor stuff. I also fixed the parser problems with the #button command and with trigger and button IDs. I also fixed the #T+ to work with a trigger/class that is disabled (since it was disabled, it wasn't finding it in the database search, so it couldn't re-enable it).

Looks like multiple sessions are almost working. It doesn't disconnect you anymore when you open a new session. There is a complex issue involved in loading a second session since it tries to restore the docking layout of the second session. I think I'll just need to make it so that the docking layout isn't restored when you load a second session into an existing session.

But once the sessions are loaded, it seems to be doing a good job keeping them separate and not confusing or overwriting them.

There are several "tweaks" that need to be made to how some windows are initially created and docked. Also, it looks like the Autolog triggers are not being created in the correct package. Looks like CMUD tries to create them in the Default settings, which I need to fix.

As I posted over in the CMUD Beta forum, I'm unsure about the current user interface for multiple trigger and button states. I probably won't change this in 1.06. It's a good thing to work on and change in 1.07 when I put the Gauges back into CMUD. But I'd rather get people using 1.06 soon since it fixes so many fundamental problems with sessions.

If all continues to go well, you can expect to see 1.06 either Wed or Thurs. Stay tuned!
Reply with quote
Rainchild
Wizard


Joined: 10 Oct 2000
Posts: 1551
Location: Australia

PostPosted: Wed Sep 13, 2006 6:20 am   
 
Got a suggestion for docking and multiple sessions... I might have my immortal loaded with a tell window, then I might decide to log on my mortal to join a group, my mortal would also have a tell window, along with a docked mapper and a few other bits and pieces (maybe some buttons or something), so what I'm thinking is put my first session inside its own container/panel type thing, then create a second container and load my mortal's layout into that, then by default display those two separate layouts on separate main tabs, but allow docking of the entire layout container as per normal, so you could have the two containers side-by-side on a widescreen monitor for example, also you could say shrink the size of the immortal's container to give more real estate to the mortal's container.

Does that make any sense with the docking complexities you were running into... treat all the sub-windows from a session as if they were a single large window which could then be docked against another single large window.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Sep 13, 2006 6:32 am   
 
Rainchild, I think it's easier to just run 2 copies of CMUD for that. Especially if you have desktop icons for each session. That's a quick and easy way to do it.

The problem is that the window docking system can only load one layout at a time. When a layout is loaded, all windows are hidden and only the windows stored in the layout are made visible and rearranged. So, when you open the 2nd session, the existing windows of your first session get messed up if you load a new layout.

Also, there is the issue of multiple copies of the same package. Only one copy of each package can be in memory at one time. If your 2 sessions have a Tell window that is stored within different session package files, then that's fine. But if they were both using a "Tells Package", then you'd have two sessions sharing the same package, which probably isn't what you want.

All of these problems go away if you just run 2 copies of CMUD. Of course, the downside is that the two copies can't communicate easily.

The other way this can be handled is to go ahead and load both sessions, and then rearrange all of the windows the way you want them, and then save this layout as a *new* session. This new session would automatically log in both your mortal and immortal and would restore the super window layout that you set up for all of your windows.

But unfortunately it mostly comes down to the docking manager. I'm using the best set of components that I could fine (much better than the docking system in DevExpress, and much^2 better than what zMUD had). But they still have a restriction of only a single docking manager component per form, and this means only one docking layout loaded at a time. The layout can be as complex as you want, with multiple containers and sub-windows. So setting up the multi-session layout isn't the problem...the problem is loading one existing layout into an already active layout.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Sep 13, 2006 9:15 pm   
 
Cool! I think I actually got the docking system to let me load multiple layouts!

When the docking system loads a layout, it first goes through and hides all existing windows. When it loads a layout file, any window in the layout is made visible. But it doesn't actually destroy the other windows.

So, when I was playing with loading multiple layouts, I discovered that the low-level docking panels for the previous session were all still there. They were just hidden.

I have previously modified CMUD so that hiding a MUD window actually closed it. Then I added a different "hide" method that would hide the window without closing it. So, when the docking system was hiding the MUD windows, they were getting destroyed. So I simply added a flag to tell CMUD whether a layout was in the process of being loaded. When a layout is being loaded, MUD windows are blocked from being destroyed. So they just get hidden.

Then, after the layout is loaded, I loop through the existing windows and make any window that was visible before the layout was loaded visible again. Viola! The previous session windows are now visible again!

Any window that existed in both layouts (like the mapper window, or the flyout settings panel, etc) gets set to the position as stored in the most recently loaded layout.

When CMUD closes and saves the sessions, the new layout with the windows from both sessions gets saved into both sessions. However, this turns out to be fine. If those sessions are later loaded, CMUD ignores any non-existing windows that are in the layout file when it is loaded.

There's probably some quirks with this, but it seems to work as least as well as zMUD did in handling multiple session layouts.
Reply with quote
Zugg
MASTER


Joined: 25 Sep 2000
Posts: 23379
Location: Colorado, USA

PostPosted: Wed Sep 13, 2006 10:08 pm   
 
Wow, I found another really nasty bug in CMUD today. This could have caused all sorts of crashes in 1.05 depending upon how much drag/drop you used in the settings editor.

Basically, CMUD 1.05 and earlier has a problem loading any setting that is assigned to a class (or module/window) where the setting record comes before the class record in the database. Normally, classes are created first, so they come before the settings in the database. So normally it worked fine. But if you created a new class and then dragged an existing setting into the new class, then this bug was triggered. When loading the database the next time, it would get to the setting first, which would then reference a class that hasn't been loaded yet, resulting in problems.

It took a while, but I got that fixed in 1.06. Rather than kludging it (forcing settings to be after classes in the database), I fixed the code so that it goes through and "fixes" the class pointers after the database is fully loaded.

This wasn't on my list to fix, but I'm glad I found it!

Still doing more testing. I think I should just go ahead and say the "end of the week" for 1.06 and stop worrying about it. I really want to have enough time to do more testing myself with 1.06.
Reply with quote
Seb
Wizard


Joined: 14 Aug 2004
Posts: 1269

PostPosted: Thu Sep 14, 2006 1:19 am   
 
Zugg wrote:
Cool! I think I actually got the docking system to let me load multiple layouts!

Cool, indeed! Nice one!

What happens to windows that are saved in several loaded layouts in the same position though? Do they just lie on top of each other until one moves them manually?
Reply with quote
Display posts from previous:   
Post new topic   Reply to topic     Home » Forums » Zugg's Blog All times are GMT
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
© 2009 Zugg Software. Hosted on Wolfpaw.net