From: Alex B. <al...@fr...> - 2004-07-21 03:08:48
|
With the current discussion of adding a map filter, I figured it would be a good time to post this. Back in November of last year I was working on improving the filters to allow you to define filters based on the RULES section. My first attempt was mentioned in this list. See: http://sourceforge.net/mailarchive/message.php?msg_id=6639951 I got as far as having the filter working (not the best code - should be using more Glib data structures etc) and just started on the GUI front end. I think I basically completed the GUI in Glade, but I did not finish the code for the edit boxes etc. Here are a couple examples of what the filter would look like (first line just describes it) in your ~/.qf/config file: version contains the string 'linux' 1,true,version,=~,linux map contains the string 'q2dm' 1,true,map,~=,q2dm time limit is >5 and less than 30 1,true,timelimit,>,5,1,true,timelimit,<,30 I have not had the time to finish the filters, so I am posting the code I have so far here, hoping someone else wants to take the time to clean up the code and finish the GUI. Maybe I'll get to it some day, but there's no point having is sitting on my computer when other people may be able to finish it. I'm not sure if the patch will even apply to the current CVS tree as I have not tried since last November. I have a .tgz file with both patch-xqf-rule-filter2 and my latest patch-xqf-rule-filter3 patch. It looks like patch-xqf-rule-filter3 contains the start of the new GUI code. I have also included the Glade file. I was discussing the filters with Ludwig back in November. Here is part of an email from myself to Ludwig describing the patch. The email was based on patch-xqf-rule-filter2. ************ This version uses a single function 'logical_expression_check' to do the logical comparisons. It is now used for the regular rules such as ping, full, empty etc plus the 'rules' section. The filter entry is still called 'rule' although it's not limited to just the rules sections. I just have to rename it. The new format of a filter contains 5 parts: 1) type of filter - 0=regular, 1=rule, 2=rule exists 2) true or false (NOT) 3) variable name 4) operator 5) value If 1) is 0, then it's a 'regular' filter as in it's not based on the rules section. This includes ping, server full, number of retries etc. If 1) is 1, then it looks it up in the rules section. This is basically what I did before without the 'required' part. If 1) is 2, then it just checks to see if that rule exists. Example: give me all servers that are running BW-Admin regardless of value. 2) If you want to 'NOT' the result, use 'false'. Otherwise, use 'true' 3) predefined variable name (ping, retries, players, full, empty, cheats, password, game, game_type, map, server_name), or the variable name in the rules section 4) operators: numeric equals: == text equals: eq text contains: ~= greater than: > greater or equal: >= less than: < less or equal: <= Obviously if you compare 3.20 with 3.2 using 'eq', it will be false. If you use ==, it will be true. Here are some examples of filter entries. An example of my filter 4: 4/filter_name=Rule Test 4/rule=0,true,ping,<,9999 Other examples: ping is less than 9999: 0,true,ping,<,9999 ping is not greater than 100: 0,false,ping,>,100 ping is not greater than 100: 0,true,ping,<=,100 number of retries is less than 2: 0,true,retries,<,2 server is not full: 0,false,full,0,0 server is full: 0,true,full,0,0 server is not empty: 0,false,empty,0,0 server is empty: 0,true,empty,0,0 cheats are not allowed: 0,false,cheats,0,0 cheats ARE allowed: 0,true,cheats,0,0 password is not required: 0,false,password,0,0 password IS required: 0,true,password,0,0 game contains the string 'battle' 0,true,game,=~,action game IS 'battle' 0,true,game,eq,battle game type contains the string 'osp' 0,true,gametype,=~,osp version contains the string 'linux' 1,true,version,=~,linux version is 3.21: 1,true,version,==,3.21 version is 3.20 or higher: 1,true,version,>=,3.20 (note: atoi is used on the version string, which could be for example: 3.21 x86 Oct 16.... atoi pulls out 3.21 and stops which is why this works) map contains the string 'q2dm' 1,true,map,~=,q2dm server name does NOT contains the string 'clan': 0,false,server_name,=~,clan time limit is >5 and less than 30 1,true,timelimit,>,5,1,true,timelimit,<,30 Notes: -when I say 'filter' below, I am referring to the above defined filters (0,false,server_name,=~,clan), NOT the filter definition (such as Rule Test) -this patch disables all the other filters that can be defined in the the filter edit menu except for the country filter -if you edit a filter in the GUI, it will erase your hand-made filters - so don't use the GUI -all the existing filters seem to fit nicely (in the GUI), except the country filter which is why I didn't touch it -you can combine multiple (up to 10) of these filters on one line -the code still uses a not very good method of storing the filters in memory. I haven't tried using over 10, so don't bother trying yourself -all filters are basically 'ANDed' together, which means you can't look for 'all servers with a port of 27910' and 'all servers without a port defined' as you could with my previous patch. They would cancel each other out. -A new 'language' of some sort would still be nice to allow grouping using (), AND, OR etc. What I have done 'should' be able to fit into that by adding the logic parser to 'server_pass_filter' and moving the current 'server_pass_filter' to a new function. It's not something I indend to work on right now. -Some filters such as game and gametype could be done using a 'rule' type filter instead of a regular filter. The reason I kept a 'regular' filter is so that it compares against s->game which matches the Game column. Each game is different, so matching the Game column is best. (For Quake2, s->game is the same as the 'gamename'. For UT, the Game column is the 'gametype' rule) The same applies for the cheats filter. You could just look for cheats' in the rules, but some games may use different variable names to represent cheats. This keeps the 'regular' filters easy for users to use. -The logical_expression_check function expects everything in strings, so it's not the most efficient way when passing existing integers. For example, ping converts the integer to a string, and then passes it to logical_expression_check. I'm not sure if this really matters, but trying to have two 'logical_expression_check' type functions depending on the type of value being passed would be a pain. ************ Here are some other discussions between Ludwig and I that should be considered: ============= Alex: -this patch disables all the other filters that can be defined in the the filter edit menu except for the country filter Ludwig: I wouldn't disable all. It's faster to just click "not full". If the user clicked something in the "easy" dialog, those settings should take precendence over the more complex filters. ============= Alex: -Some filters such as game and gametype could be done using a 'rule' type filter instead of a regular filter. The reason I kept a 'regular' filter is so that it compares against s->game which matches the Game column. Each game is different, so matching the Game column is best. (For Quake2, s->game is the same as the 'gamename'. For UT, the Game column is the 'gametype' rule) The same applies for the cheats filter. You could just look for 'cheats' in the rules, but some games may use different variable names to represent cheats. This keeps the 'regular' filters easy for users to use. Ludwig: Good. ============= Alex: + /* Filter for custom rule */ + if( filter->rule && *filter->rule) + { + //printf("RULE!!\n"); Ludwig: -you may use debug(0,"foo") instead. debug() displays file, function and line. ============= Alex: + char* rule_copy = g_strdup(filter->rule); + static const char delim[] = ",\n\r"; + int match = 0; + + char* rules[50]; + int num = 0; + int i; + char buf[10]; + + char *type = NULL; + char *truefalse = NULL; + char *varname = NULL; + char *operator = NULL; + char *value = NULL; + + rules[num++] = strtok(rule_copy, delim); Ludwig: use g_strsplit ============= Alex: +int logical_expression_check (const char *truefalse, const char *left, const char *operator, const char *right) +{ + int match = 0; + + if (!truefalse || !left || !operator || !right) + return FALSE; + + //printf("logical_expression_check: %s %s %s\n", left, operator, right); + + if(strcasecmp( operator ,"eq") == 0){ + //printf("Rule: eq\n"); + if(strcasecmp( left, right ) == 0){ + match = 1; // Found! + } Ludwig: operator should be an enum, then you can use switch(operator) {...}. String compares are expensive. ============= Alex: +} + /* - This applies a filter's attributes to a server entry and returns true if it - passes the filter or false if not. + This applies a filter's attributes to a server entry and returns TRUE if it + passes the filter or FALSE if not. */ static int server_pass_filter (struct server *s){ [...] + + adv_filter = g_strsplit(adv_filter_copy, delim, 0); Ludwig: Parse the string when reading the config file. Doing that here is extremely expensive, the function is called (multiple times) for each server. Remember Half-Life has >30000 servers! See below. + /* A regular 'easy' filter based on pre-defined variable names */ + if (strcmp (type, "0") == 0) ^ enum + { + if (strcasecmp (varname, "ping") ==0) { ^^^^ enum ============= Alex: @@ -460,6 +717,24 @@ filter->game_type = config_get_string("game_type"); filter->map_contains = config_get_string("map_contains"); filter->server_name_contains = config_get_string("server_name_contains"); + + /* Advanced filters */ + char buf[30]; + int j=1; + + while(1) { + snprintf( buf, 30, "%d/advanced", j); Ludwig: maybe "rule%d" instead? start j at zero. Parse the string you get here directly into a struct to speed up processing in server_pass_filter. e.g: enum { RF_EQ, RF_STREQ, RF_LT, ... , RF_EXISTS } server_rule_filter_op; enum { RF_RULE, RF_PING, RF_PLAYERS, RF_MAP, ... } server_rule_filter_type; struct server_rule_filter { server_rule_filter_type type; gboolean negate; const char* key; // only used with RF_RULE server_rule_filter_op op; const char* value; // not honored by RF_EXISTS }; ============= Alex |