21 Jul, 2011, quixadhal wrote in the 61st comment:
Votes: 0
Things should never "fall out of whack" in a relational database. That's why they have transactions. If your DB doesn't support transactions, it's just a glorified text file that understands the SQL query language.
21 Jul, 2011, David Haley wrote in the 62nd comment:
Votes: 0
That said, there is an awful lot of value in a "glorified text file that understands the SQL query language"…… no need to be so dismissive of rich table formats with joining across tables and a powerful query language.
22 Jul, 2011, quixadhal wrote in the 63rd comment:
Votes: 0
Hehehe, touche! :)
22 Jul, 2011, Tyche wrote in the 64th comment:
Votes: 0
plamzi said:
And, to add to the list of benefits of a standalone db, I can now do:

call worldReplace('massice', 'massive');

Planning to be typo-free by tomorrow! ;)


Is worldReplace a typo for wordReplace? ;-)

You are so tempting me to just throw in with those who suggest that all you need is bash and grep. ;-)
Maybe sed…
$ mv help.are help.are.bkup & sed -e 's/massice/massive/' <help.are/bkup >help.are

Let's use a perl one-liner. Let's spell fix all our Merc area files at once…
$ perl -pi~ -e 's/massice/massive/g' *.are

You don't even need unix tools. You can do this also with just the Windows command shell.

Don't get me wrong. I understand the value of RDBMSs. I spent around 13 years of my career as a DB2 - SQL/DS DBA
Anyway I don't think it's the ideal solution for any/every mud though.
There are better options than either text files or RDBMSs.
22 Jul, 2011, plamzi wrote in the 65th comment:
Votes: 0
Tyche said:
plamzi said:
And, to add to the list of benefits of a standalone db, I can now do:

call worldReplace('massice', 'massive');

Planning to be typo-free by tomorrow! ;)


Is worldReplace a typo for wordReplace? ;-)

You are so tempting me to just throw in with those who suggest that all you need is bash and grep. ;-)


I actually agree. Bash+grep+sed+vim is still the world's most powerful text editing suite. We should all erase our copies of Microsoft Word and Notepad++, take to a street corner, and spread wisdom on recursive regexes, multiline sed scripts, and 1001 vim shortcuts, which (we all know) can do everything in the known universe.

And since you're better at it than I am, I'm sure you'll be able to quickly supply the one-liner which scans all your world files, detects all possible errors against a good dictionary, prompts you with correct options, as well as the option to "ignore all", and only after you confirm goes on to do the global replace. Anything less, and you'll end up with a highly un-bootable world, no?

OK, I got tempted, too. :evil:
22 Jul, 2011, Rarva.Riendf wrote in the 66th comment:
Votes: 0
Well, I would have use notepad++……find word an replace in all files in area directory….No need for any knowledge, some clicks, doable by anybody.
22 Jul, 2011, quixadhal wrote in the 67th comment:
Votes: 0
plamzi said:
And since you're better at it than I am, I'm sure you'll be able to quickly supply the one-liner which scans all your world files, detects all possible errors against a good dictionary, prompts you with correct options, as well as the option to "ignore all", and only after you confirm goes on to do the global replace. Anything less, and you'll end up with a highly un-bootable world, no?

OK, I got tempted, too. :evil:


You mean ispell?
Call via: find . -type f | xargs ispell, if you want it to run through all the files in the current directory.
22 Jul, 2011, plamzi wrote in the 68th comment:
Votes: 0
quixadhal said:
plamzi said:
And since you're better at it than I am, I'm sure you'll be able to quickly supply the one-liner which scans all your world files, detects all possible errors against a good dictionary, prompts you with correct options, as well as the option to "ignore all", and only after you confirm goes on to do the global replace. Anything less, and you'll end up with a highly un-bootable world, no?

OK, I got tempted, too. :evil:


You mean ispell?
Call via: find . -type f | xargs ispell, if you want it to run through all the files in the current directory.


No, no, no, that's cheating! :) That's like me using
pspell for php to scan the database, a specialized tool to make the job quick and easy (and which I'm planning to use in my web OLC project btw). Anyone can use a specialized tool. But the claim here, as I understand it, is that all you need is bash / grep / sed.

I have never claimed that a database makes impossible things possible, but I do claim that it makes a whole lot of things easier. One reason being that there's lots of ready-made stuff you can just plug into it and go, especially when it comes to opening up your game to the web. If you can do that with bash / grep / sed, all the more power to you.
23 Jul, 2011, Tyche wrote in the 69th comment:
Votes: 0
plamzi said:
I actually agree. Bash+grep+sed+vim is still the world's most powerful text editing suite. We should all erase our copies of Microsoft Word and Notepad++, take to a street corner, and spread wisdom on recursive regexes, multiline sed scripts, and 1001 vim shortcuts, which (we all know) can do everything in the known universe.


Why delete Word when you could the exact same thing with macros. Or even create an entire GUI builder application inside Word that spits out properly formatted Merc area (or even Open Office if you prefer). I could do it with REXX in a TSO shell. Text processing is quite well developed and capable tools exist for any platform. My point was that an RDBMS doesn't give you any advantage in processing text. Not that I endorse flat text files over an RDBMS either.

You can do the equivalent of SQL (and far far more powerful queries and modifications) in some muds using their own built-in language.
For example:
public method .nerf_players_cmd() {
var l , x;
for x in ($player.children()) { # select * from players
if (x.strength() > 20) { # where strength > 20
l = l.add(x);
}
}
l = l.sort.mmap('name); # order by players.name;

for x in (l)
.tell(x.name); # cursor over result set

for x in ($player.children()) { # update players
# set strength = 20
if (x.strength() > 20) { # where strength > 20;
x.strength = 20;
}
}

for x in ($player.children()) { # delete from players
if (x.strength() < 5) { # where strength < 5;
x.destroy;
}
}
}
23 Jul, 2011, plamzi wrote in the 70th comment:
Votes: 0
Tyche said:
My point was that an RDBMS doesn't give you any advantage in processing text. Not that I endorse flat text files over an RDBMS either.

You can do the equivalent of SQL (and far far more powerful queries and modifications) in some muds using their own built-in language.
For example:


This is a curious post because it seems like you're not really trying to make the point you said you'd make. That's not surprising given that you yourself worked on converting Merc to use sqlite. I believe that since you engaged in a process very similar to what I did, you're well aware of the multiple advantages a relational database offers in processing text and that maybe you were just in an adversarial mood.

I'm not going to dwell on your examples because they don't even show a simple join equivalent, they assume that the data you want modified is already loaded in the memory, and they don't show that in order to save even those superficial changes a flat-file codebase will need to rewrite all affected player files, and a binary-file codebase (like mine used to be prior to the conversion) will have to rewrite the entire player file. In other words, the examples are selectively simplistic.

To hopefully push the discussion beyond the "oh but you can do that with [], too" point, I'm going to post a few (out of many) examples of things I've done with the database back-end that I believe would be much more difficult (note that I'm not saying impossible) to do without one:

sprintf(buf, "INSERT INTO PlayerBonds SET ID='%ld.%ld', Strength=1 ON DUPLICATE KEY UPDATE Strength=if(Strength > 99, 100, Strength+1)", GET_IDNUM(ch), GET_IDNUM(ch));
query(buf);


Explanation: Initialize a player property or increment it by one if it already exists, but don't go over 100. This code runs once every 30 sec. for all players in game, so I'm not interested in knowing right away what the value of this property is currently. I'll fetch it in a few minutes. For now, just do the update, and don't slow the game down.

void save_char_stat (struct char_data *ch, int stat)  {
switch (stat) {
case PLAYER_LEVEL:
sprintf(buf, "UPDATE PlayerData SET Level='%d' WHERE PID=%ld", GET_LEVEL(ch), GET_IDNUM(ch));
break;
case PLAYER_EXP:
sprintf(buf, "UPDATE PlayerPoints SET Exp='%d' WHERE PID=%ld", GET_EXP(ch), GET_IDNUM(ch));
break;
case PLAYER_GOLD:
sprintf(buf, "UPDATE PlayerPoints SET Gold='%d' WHERE PID=%ld", GET_GOLD(ch), GET_IDNUM(ch));
break;
case PLAYER_BANK_GOLD:
sprintf(buf, "UPDATE PlayerPoints SET BankGold='%d' WHERE PID=%ld", GET_BANK_GOLD(ch), GET_IDNUM(ch));
break;
}
query(buf);
}


Explanation: Make sure that certain player properties can be saved individually more often than others, without having to rewrite the entire set of saved player parameters.

delete from PlayerAliases where PID = 37673;
insert into PlayerAliases SELECT Alias, Replacement, Type, '37673' as PID, NUM, replace(inum, '37187', '37673') as INUM FROM PlayerAliases where pid = 37187;

Explanation: Allow a player to copy his/her set of aliases/macros to an alt without having to read the aliases into the memory and back (i. e. the target alt can be offline).

$result = mysql_query("SELECT pd.Class, pd.Name, round(pp.Exp/psd.Kills,0) from PlayerData pd 
join PlayerPoints pp on pp.PID = pd.PID
join PlayerSpecialData psd on psd.PID = pd.PID
where pd.Level < 61 and LastLogon > 1295805460
and pd.name != 'Kikaha' and pd.name != 'Drevram'
and pd.Level > 10 and class = " . $i . " order by round(pp.Exp/psd.Kills,0) desc limit 1;");


Explanation: Create a leaderboard ranking table showing "Heaviest Hitters by Class", calculating on the fly the avg. experience gained per kill for all recently active mortals over level 10, excluding some test chars, and showing only the top character in each class. Provide that data without interfering with the operation of the game server in any way.


call getQuestsFor('Kikaha');

– ——————————————————————————–
– Routine DDL
– ——————————————————————————–
DELIMITER $$

CREATE DEFINER=`root`@`%` PROCEDURE `getQuestsFor`(id INT, lvl INT)
BEGIN
SELECT ql.* FROM QuestLib ql WHERE ql.Published = 1 AND ql.QuestNPC != -1 AND ql.QuestScrollCount != 0 AND ql.LevelMin <= lvl
AND (
ql.QuestCount = -1 or (select count(*) from PlayerQuestHistory qh1 where qh1.PID = id and qh1.QID = ql.QID) = 0
OR ql.QuestCount < (select `Count` from PlayerQuestHistory qh2 where qh2.PID = id and qh2.QID = ql.QID)
)
ORDER BY ql.Title;
END


Explanation: Provide the player with a list of all currently published quests that he or she is able to join. Base the results on their current level and check their entire quest history in the case of quests that can only be done once. Do that without having to load their quest history into the server memory.

There's many more I can post, but I don't think that's necessary. And to be honest, I think my basic point doesn't really need to be proved because I believe the esteemed members of this community are actually in agreement but don't want to have any points taken off for insufficient arrogance. While it's been somewhat fun to play against that, I'm beginning to grow weary of every subject eventually boiling down to a pi**ing match. So I'm going to take an indefinite break and get some work done. I'll just have to resist the temptation to share details about it in these forums…
23 Jul, 2011, Tonitrus wrote in the 71st comment:
Votes: 0
plamzi said:
There's many more I can post, but I don't think that's necessary. And to be honest, I think my basic point doesn't really need to be proved because I believe the esteemed members of this community are actually in agreement but don't want to have any points taken off for insufficient arrogance. While it's been somewhat fun to play against that, I'm beginning to grow weary of every subject eventually boiling down to a pi**ing match. So I'm going to take an indefinite break and get some work done. I'll just have to resist the temptation to share details about it in these forums…


I will describe my general posting procedure as of the last year, both new topics and responses. I type up the post. I preview and edit it 30 times. I think about the responses, both the potentially useful ones and the useless. I think about the likely quibbling, sophistry, and the 37 restatements of every point mentioned in my initial post that will be necessary to make various people understand that their responses have nothing whatsoever to do with what I'm talking about. Then I delete my post and find something better to do.

Now, while the situation is not unique to mudbytes, I've seen better, and it's usually not worth it to post here. You'll save yourself a lot of grief by just not bothering to post the majority of the time, and by barely reading most posts. It sucks, but there it is.

Best of luck with your getting work done.
24 Jul, 2011, Tyche wrote in the 72nd comment:
Votes: 0
plamzi said:
Tyche said:
My point was that an RDBMS doesn't give you any advantage in processing text. Not that I endorse flat text files over an RDBMS either.

You can do the equivalent of SQL (and far far more powerful queries and modifications) in some muds using their own built-in language.
For example:


This is a curious post because it seems like you're not really trying to make the point you said you'd make.


There are multiple points.

In my first post…
Tyche said:
Standalone Relational Database Y/N?
Maybe.
It'd be a step back to the 80's, if your mud server already implements an object-oriented database.
A step forward if you're using a typical Diku.


And later after the MyISAM digression …
Tyche said:
Anyway I don't think it's the ideal solution for any/every mud though.
There are better options than either text files or RDBMSs.


You don't seem at all curious about anything other than a YES answer.

plamzi said:
That's not surprising given that you yourself worked on converting Merc to use sqlite. I believe that since you engaged in a process very similar to what I did, you're well aware of the multiple advantages a relational database offers in processing text and that maybe you were just in an adversarial mood.


I certainly did not add RDBMS support because of its text processing capabilities. Murk++ doesn't use any SQL built-in functions anyway. RDBMSs offer a very poor set of text processing functions compared to what most muds already provide. I've also worked on the Cold Project and TeensyMud. TeensyMUD can use YAML, XML, GDBM, SDBM, DBM, or SQLITE as a backend. Don't confuse usage with advocacy.

plamzi said:
I'm not going to dwell on your examples because they don't even show a simple join equivalent, they assume that the data you want modified is already loaded in the memory, and they don't show that in order to save even those superficial changes a flat-file codebase will need to rewrite all affected player files, and a binary-file codebase (like mine used to be prior to the conversion) will have to rewrite the entire player file. In other words, the examples are selectively simplistic.


I don't think you understand. The example IS an object-oriented database language. The superficial similarities between tables and prototypical objects lend themselves to some initial simple comparisions. Merely referencing an object causes it to be loaded from disk into the cache.

Joins are even simpler as any object can hold a reference to another object.
for plr in ($player.children()) { 
for obj in plr.inv() {
.tell(obj.name());
}
}


TeensyMud does this with meta-programming by storing references to objects as database references. The objects are on disk and loaded into and out of the cache as needed. It should be transparent to the programmer. "Schema" redefinition is automatic, just add another property.

class GameObject < Root
property :location, :contents, :triggers, :msgfail, :msgsucc
end


The look command in a stock Diku is a three table join. It doesn't require SQL.


Schema creation is easy in Cold.
var o;
o = create($root); # create table $foo
o.set_objname('foo);
o.add_var(a); # (a int,
o.add_var(b); # b int);


Consider modifying a C/C++ mud to do the same thing with lazy loading of objects, rooms or players. There's no reason that players not logged in have to be inaccessible to the mud, and no reason all rooms or object have to be in memory at all times. The entire Tiny server family has no problem whatsoever in accessing all player objects. I don't believe LPMuds have a problem either.

plamzi said:
To hopefully push the discussion beyond the "oh but you can do that with [], too" point, I'm going to post a few (out of many) examples of things I've done with the database back-end that I believe would be much more difficult (note that I'm not saying impossible) to do without one:

sprintf(buf, "INSERT INTO PlayerBonds SET ID='%ld.%ld', Strength=1 ON DUPLICATE KEY UPDATE Strength=if(Strength > 99, 100, Strength+1)", GET_IDNUM(ch), GET_IDNUM(ch));
query(buf);


Explanation: Initialize a player property or increment it by one if it already exists, but don't go over 100. This code runs once every 30 sec. for all players in game, so I'm not interested in knowing right away what the value of this property is currently. I'll fetch it in a few minutes. For now, just do the update, and don't slow the game down.


I don't know why one would, but even missing the context and information.
You can do that too with…
@program $sys.update_strength() +flags=['forked]

var x, s;
for x in ($players.descendants()) {
catch any {
s = x.get_var('strength);
if (s > 99)
x.set_var(100);
else
x.set_var(s + 1);
} with {
x.add_var('strength);
x.set_var('strength,1);
}
}
$scheduler.add_task(30,'update_str);
.

@eval $scheduler.add_task(30,'update_str); # start it going


One of many possible ways.

plamzi said:
Explanation: Make sure that certain player properties can be saved individually more often than others, without having to rewrite the entire set of saved player parameters.

plamzi said:
Explanation: Allow a player to copy his/her set of aliases/macros to an alt without having to read the aliases into the memory and back (i. e. the target alt can be offline).


Neither would be relevant if one implemented implicit persistence mechanisms.
Still MySQL like most RDBMSs is row based, not column based.
There is no performance difference in updating all the columns of a row at once or just one of them.

plamzi said:
Explanation: Create a leaderboard ranking table showing "Heaviest Hitters by Class", calculating on the fly the avg. experience gained per kill for all recently active mortals over level 10, excluding some test chars, and showing only the top character in each class. Provide that data without interfering with the operation of the game server in any way.


I don't know why you think adding conditionals and joins somehow complicates the query.
If I assume that a $player holds a reference to $playerdata in pd, $playerpoints in pp and $playerspecialdata in psd
then…
.heavy_hitters() {
args class;
dict = #{};
for pl in ($players.descendants()) {
if (pl.pd().Level() < 61 && pl.LastLogon() > 1295805460) {
if (pl.pd().Name() != "Kikaha" && pl.pd().Name() != "Drevan") {
if (pl.pd.Level() > 10 && pl.Class() == class) {
dict = dict.add($math.round(pl.pp().Exp() / pl.psd().Kills()), pl.pd().Name());
}
}
}
}
.tell(dict.explode_in_english());
}


You have the advantage of setting this up beforehand. Note that in every reference is a function call.
Might one be wiser spending the time converting your Diku C mud to C++ and encapsulating all the pointer
references (e.g. smart pointers) in member calls to a persistant store?

plamzi said:
Explanation: Provide the player with a list of all currently published quests that he or she is able to join. Base the results on their current level and check their entire quest history in the case of quests that can only be done once. Do that without having to load their quest history into the server memory.


Of course it gets loaded into the servers memory. I don't know why you distinguish between which process has to load it.

plamzi said:
There's many more I can post, but I don't think that's necessary. And to be honest, I think my basic point doesn't really need to be proved because I believe the esteemed members of this community are actually in agreement but don't want to have any points taken off for insufficient arrogance. While it's been somewhat fun to play against that, I'm beginning to grow weary of every subject eventually boiling down to a pi**ing match. So I'm going to take an indefinite break and get some work done. I'll just have to resist the temptation to share details about it in these forums…


I don't do meta.
24 Jul, 2011, Ssolvarain wrote in the 73rd comment:
Votes: 0
For comparison.

UWD-l-tOO..."> UWD-l-tOO..." type="application/x-shockwave-flash" width="425" height="350">
24 Jul, 2011, Runter wrote in the 74th comment:
Votes: 0
27 Jul, 2011, plamzi wrote in the 75th comment:
Votes: 0
Tyche is absolutely correct that all the wonderful things I've shared here in naive excitement can be done in C++. One can indeed convert an existing DikuMUD to C++, point it to a persistent object storage solution, integrate a web server with proper multithreading, and achieve the exact same results. Tyche seems to think that this will take less time than what I've done, but that's the opinion of someone highly competent in C++ who has no code to share to make such a project easier. I, on the other hand, have a working solution and can share code that can cut your implementation down to days. You pick your horse.

Runter is absolutely correct that spell-checking your world files can be done with shell utilities. I apologize if I ever suggested that converting your DikuMUD to use MySQL is the only way to spell-check your world files.

quixadhal is absolutely correct that an SQL implementation without InnoDB and transactions on all database calls is unworthy of his attention. He is undoubtedly already elsewhere.

I humbly withdraw from the battlefield, in utter, shameful, defeat.
27 Jul, 2011, David Haley wrote in the 76th comment:
Votes: 0
Plamzi said:
I, on the other hand, have a working solution and can share code that can cut your implementation down to days.

Well, whatever you think of all the rest, it would at least be useful to share this… hopefully somebody will benefit, even if Tyche will use C++ and Rarva will use grep. :wink:
27 Jul, 2011, Rarva.Riendf wrote in the 77th comment:
Votes: 0
Sometimes I think I do not write the same english than you do on this forum….
I will probably use Plamzi code for everything that is area related. But yes I will keep using grep and ingame tools on players though. And you can keep saying am a bad coder / bad practice giver for all I care.
27 Jul, 2011, Ssolvarain wrote in the 78th comment:
Votes: 0
English? I thought this was a movie about a bunch of walruses fighting over who get next turn on the giant stuffed duckie of luv.
27 Jul, 2011, quixadhal wrote in the 79th comment:
Votes: 0
*chuckle*

Well, for my part in this, I merely consider a lack of transaction support to be both crippling and outright dangerous when dealing with a relational database (SQL or not). It makes your life difficult, because of exactly some of the issues Planzi noticed, data can get "out of sync", because of code errors causing some tables to be updated while others are not. Transactions prevent that, if you use them carefully, as your updates either happen in their entirety, or they don't happen at all.

Dangerous, because a database error (such as your disk running out of space) will corrupt your entire database. Databases which use transactions usually write things to a transaction log which is later merged into the actual table structure as it gets flushed from buffers. A disk error will usually corrupt the transaction log, but you have a chance that the tables themselves will remain intact (depends on how it works, and how much memory you have, and a bunch of other stuff I'm sure).

That said, if you're happy with the results, I'm sure it would be appreciated by folks trying to do the same kind of thing (or those curious about seeing examples of such things). Don't let us grumpy perfectionists dissuade you. :)
27 Jul, 2011, Tyche wrote in the 80th comment:
Votes: 0
plamzi said:
Tyche is absolutely correct that all the wonderful things I've shared here in naive excitement can be done in C++. One can indeed convert an existing DikuMUD to C++, point it to a persistent object storage solution, integrate a web server with proper multithreading, and achieve the exact same results. Tyche seems to think that this will take less time than what I've done, but that's the opinion of someone highly competent in C++ who has no code to share to make such a project easier. I, on the other hand, have a working solution and can share code that can cut your implementation down to days. You pick your horse.


I think a more long term beneficial use of one's time would be to convert one's Dikumud to C– (or Ruby, Python, etc.),
but if C and C++ still float your boat go for it. Even 400 hours is way more than enough time to convert a Diku to
a language and tools I've never even used before.
Anyway nobody is going to argue with snippets.
If your snippet is any good, maybe I'd install it into Murk++.
60.0/81