source: tags/ms_r16q4/SL/ITEM_SHADER/field_shader.cxx

Last change on this file was 15279, checked in by westram, 8 years ago
File size: 24.9 KB
Line 
1// ============================================================ //
2//                                                              //
3//   File      : field_shader.cxx                               //
4//   Purpose   : a shader based on 1 to 3 item fields           //
5//                                                              //
6//   Coded by Ralf Westram (coder@reallysoft.de) in June 2016   //
7//   http://www.arb-home.de/                                    //
8//                                                              //
9// ============================================================ //
10
11#include "field_shader.h"
12
13#include <item_sel_list.h>
14#include <awt_config_manager.hxx>
15
16#include <aw_root.hxx>
17#include <aw_awar.hxx>
18#include <aw_msg.hxx>
19
20#include <ad_cb_prot.h>
21
22#include <arb_global_defs.h>
23#include <arb_str.h>
24#include <arb_defs.h>
25
26#include <set>
27#include <limits>
28
29using namespace std;
30
31#define AWAR_DIM_ACTIVE(dim) dimension_awar(dim, "active")
32#define AWAR_FIELD(dim)      dimension_awar(dim, "field")
33#define AWAR_ACI(dim)        dimension_awar(dim, "aci")
34#define AWAR_VALUE_MIN(dim)  dimension_awar(dim, "min")
35#define AWAR_VALUE_MAX(dim)  dimension_awar(dim, "max")
36
37class FieldReader {
38    RefPtr<const char> fieldname;
39    RefPtr<const char> aci; // if NULL -> no (=empty) ACI
40
41    bool is_hkey; // true if fieldname is hierarchical
42
43    float min_value, max_value;
44    float factor;
45
46    static bool safe_atof(const char *strval, float& res) {
47        // returns true if at least some characters have been converted
48        char *end;
49        res = strtof(strval, &end);
50        return end != strval;
51    }
52
53    void calc_factor() {
54        float span = max_value-min_value;
55        factor     = span != 0.0 ? 1.0/span : 1/0.00001;
56    }
57public:
58    FieldReader() : fieldname(NULL), aci(NULL) {}
59
60    FieldReader(const char *fieldname_, const char *aci_, float minVal, float maxVal) :
61        fieldname(fieldname_),
62        aci(aci_),
63        is_hkey(strchr(fieldname, '/')),
64        min_value(minVal),
65        max_value(maxVal)
66    {
67        is_assert(fieldname);
68        calc_factor();
69    }
70
71    bool may_read() const { return fieldname != 0; } // false -> never will produce value
72
73    float calc_value(GBDATA *gb_item) const {
74        /*! reads one field from passed item.
75         *
76         * Returns NAN in the following cases:
77         * - 'this' is a null-reader
78         * - no item passed
79         * - field is missing
80         * - STRING field contains no numeric data
81         *
82         * Otherwise the value is scaled (but not limited) to the value range.
83         */
84        if (fieldname && gb_item) {
85            GBDATA *gb_field = is_hkey
86                ? GB_search(gb_item, fieldname, GB_FIND)
87                : GB_entry(gb_item, fieldname);
88
89            if (gb_field) {
90                float val = 0.0;
91
92                if (aci) {
93                    char *content   = GB_read_as_string(gb_field);
94                    char *applied   = GB_command_interpreter(GB_get_root(gb_item), content, aci, gb_item, NULL);
95                    bool  converted = safe_atof(applied, val);
96                    free(content);
97                    free(applied);
98                    if (!converted) return NAN;
99                }
100                else {
101                    switch (GB_read_type(gb_field)) {
102                        case GB_INT: val = GB_read_int(gb_field); break;
103                        case GB_FLOAT: val = GB_read_float(gb_field); break;
104                        default: {
105                            char *content   = GB_read_as_string(gb_field);
106                            bool  converted = safe_atof(content, val);
107                            free(content);
108                            if (!converted) return NAN;
109                            break;
110                        }
111                    }
112                }
113                val = (val-min_value)*factor;
114                return val;
115            }
116        }
117        return NAN;
118    }
119
120};
121
122class MultiFieldReader {
123    FieldReader reader[3];
124    int         dim;
125
126public:
127    MultiFieldReader() :
128        dim(0)
129    {}
130
131    void add_reader(const FieldReader& added) {
132        if (added.may_read()) {
133            is_assert(dim<3); // hardcoded limit
134            reader[dim++] = added;
135        }
136    }
137    int get_dimension() const {
138        return dim;
139    }
140
141    ShadedValue calc_value(GBDATA *gb_item) const {
142        switch (dim) {
143            case 0: return ValueTuple::undefined();
144            case 1: return ValueTuple::make(reader[0].calc_value(gb_item));
145            case 2: return ValueTuple::make(reader[0].calc_value(gb_item),
146                                            reader[1].calc_value(gb_item));
147            case 3: return ValueTuple::make(reader[0].calc_value(gb_item),
148                                            reader[1].calc_value(gb_item),
149                                            reader[2].calc_value(gb_item));
150        }
151        is_assert(0); // unsupported dimension
152        return ShadedValue();
153    }
154};
155
156
157typedef set<string> FieldSet;
158
159class ItemFieldShader: public ShaderPlugin {
160    BoundItemSel itemtype;
161    FieldSet     hcbs_installed; // list of currently installed hierarchy callbacks
162
163    RefPtr<AW_window> aw_config;
164
165    mutable string                     item_dbpath;
166    mutable SmartPtr<MultiFieldReader> reader;
167
168    const char *get_fieldname(int dim) const {
169        // returns configured fieldname (or NULL)
170        AW_root *awr = AW_root::SINGLETON;
171        if (awr && awr->awar(AWAR_DIM_ACTIVE(dim))->read_int()) {
172            const char *fname = awr->awar(AWAR_FIELD(dim))->read_char_pntr();
173            if (strcmp(fname, NO_FIELD_SELECTED) != 0) {
174                return fname;
175            }
176        }
177        return NULL;
178    }
179
180    static bool is_ACI(const char *aci) {
181        return aci[0];
182    }
183
184    const char *get_ACI(int dim) const {
185        // returns configured ACI (or NULL)
186        AW_root *awr = AW_root::SINGLETON;
187        if (awr && awr->awar(AWAR_DIM_ACTIVE(dim))->read_int()) {
188            const char *aci = awr->awar(AWAR_ACI(dim))->read_char_pntr();
189            if (is_ACI(aci)) return aci;
190        }
191        return NULL;
192    }
193
194    FieldReader get_dimension_reader(int dim) const {
195        AW_root *awr = AW_root::SINGLETON;
196        if (awr) {
197            const char *fieldname = get_fieldname(dim);
198            if (fieldname)  {
199                float minVal = atof(awr->awar(AWAR_VALUE_MIN(dim))->read_char_pntr());
200                float maxVal = atof(awr->awar(AWAR_VALUE_MAX(dim))->read_char_pntr());
201                return FieldReader(fieldname, get_ACI(dim), minVal, maxVal);
202            }
203        }
204        return FieldReader();
205    }
206
207    MultiFieldReader *make_multi_field_reader() const {
208        MultiFieldReader *multi = new MultiFieldReader;
209        for (int dim = 0; dim<get_max_dimension(); ++dim) {
210            multi->add_reader(get_dimension_reader(dim));
211        }
212        return multi;
213    }
214
215    MultiFieldReader& get_field_reader() const {
216        if (reader.isNull()) reader = make_multi_field_reader();
217        return *reader;
218    }
219
220    bool knows_item_dbpath() const {
221        if (item_dbpath.empty()) {
222            GBDATA *gb_item = itemtype.get_any_item();
223            if (gb_item) {
224                const char *ipath = GB_get_db_path(gb_item);
225                if (ipath) item_dbpath = string(ipath);
226            }
227        }
228        return !item_dbpath.empty();
229    }
230
231    void update_db_callbacks(const FieldSet& wanted) {
232        if (knows_item_dbpath()) {
233            DatabaseCallback dbcb = makeDatabaseCallback(ItemFieldShader::field_updated_in_DB_cb, this);
234
235            GB_ERROR  error   = NULL;
236            GBDATA   *gb_main = itemtype.gb_main;
237
238            GB_transaction ta(gb_main);
239
240            // uninstall unwanted callbacks:
241            for (FieldSet::const_iterator installed = hcbs_installed.begin(); installed != hcbs_installed.end(); ++installed) {
242                if (wanted.find(*installed) == wanted.end()) { // 'installed' is not in 'wanted'
243                    string field_db_path = item_dbpath + '/' + *installed;
244                    error                = GB_remove_hierarchy_callback(gb_main, field_db_path.c_str(), GB_CB_CHANGED_OR_DELETED, dbcb);
245                }
246            }
247            // install missing callbacks:
248            for (FieldSet::const_iterator missing = wanted.begin(); missing != wanted.end(); ++missing) {
249                if (hcbs_installed.find(*missing) == hcbs_installed.end()) { // 'missing' is not in 'hcbs_installed'
250                    string field_db_path = item_dbpath + '/' + *missing;
251                    error                = GB_add_hierarchy_callback(gb_main, field_db_path.c_str(), GB_CB_CHANGED_OR_DELETED, dbcb);
252                }
253            }
254            hcbs_installed = wanted; // store current state
255            aw_message_if(error);
256        }
257    }
258    void add_used_fields(FieldSet& wanted) const {
259        const char *fname0 = get_fieldname(0);
260        if (fname0) wanted.insert(fname0);
261    }
262    void setup_db_callbacks(bool install) {
263        FieldSet wanted;
264        if (install) add_used_fields(wanted); // otherwise uninstall all
265        update_db_callbacks(wanted);
266    }
267    static void field_updated_in_DB_cb(UNFIXED, ItemFieldShader *shader) {
268        shader->trigger_reshade_if_active_cb(SIMPLE_RESHADE);
269    }
270
271    void init_config_definition(AWT_config_definition& cdef) const;
272
273public:
274    explicit ItemFieldShader(const BoundItemSel& itemtype_) :
275        ShaderPlugin("field", "Database field shader"),
276        itemtype(itemtype_),
277        aw_config(NULL)
278    {}
279
280    ShadedValue shade(GBDATA *gb_item) const OVERRIDE {
281        return get_field_reader().calc_value(gb_item);
282    }
283
284    int get_dimension() const OVERRIDE {
285        // returns (current) dimension of shader-plugin
286        return get_field_reader().get_dimension();
287    }
288    int get_max_dimension() const {
289        return 3;
290    }
291    void init_specific_awars(AW_root *awr) OVERRIDE;
292
293    bool customizable() const OVERRIDE { return true; }
294    void customize(AW_root *awr) OVERRIDE;
295
296    char *store_config() const OVERRIDE;
297    void load_or_reset_config(const char *cfgstr) OVERRIDE;
298
299    void activate(bool on) OVERRIDE {
300        // called with true when plugin gets activated, with false when it gets deactivated
301        setup_db_callbacks(on);
302    }
303
304
305    void setup_changed_cb() {
306        setup_db_callbacks(true); // @@@ does this also happen if plugin is NOT ACTIVE? shouldn't!
307
308        reader.SetNull();
309        trigger_reshade_if_active_cb(CHECK_DIMENSION_CHANGE);
310    }
311    static void setup_changed_cb(AW_root*, ItemFieldShader *shader) {
312        shader->setup_changed_cb();
313    }
314
315    void scan_value_range_cb(int dim);
316    static void scan_value_range_cb(AW_window*, ItemFieldShader *shader, int dim) { shader->scan_value_range_cb(dim); }
317
318};
319
320void ItemFieldShader::init_specific_awars(AW_root *awr) {
321    for (int dim = 0; dim<get_max_dimension(); ++dim) {
322        RootCallback FieldSetup_changed_cb = makeRootCallback(ItemFieldShader::setup_changed_cb, this);
323
324        awr->awar_int(AWAR_DIM_ACTIVE(dim), dim == 0)       ->add_callback(FieldSetup_changed_cb);
325        awr->awar_string(AWAR_FIELD(dim), NO_FIELD_SELECTED)->add_callback(FieldSetup_changed_cb);
326        awr->awar_string(AWAR_ACI(dim), "")                 ->add_callback(FieldSetup_changed_cb);
327        awr->awar_string(AWAR_VALUE_MIN(dim), "0")          ->add_callback(FieldSetup_changed_cb);
328        awr->awar_string(AWAR_VALUE_MAX(dim), "1")          ->add_callback(FieldSetup_changed_cb);
329    }
330}
331
332void ItemFieldShader::init_config_definition(AWT_config_definition& cdef) const {
333    for (int dim = 0; dim<get_max_dimension(); ++dim) {
334        cdef.add(AWAR_DIM_ACTIVE(dim), "active", dim);
335        cdef.add(AWAR_FIELD     (dim), "field",  dim);
336        cdef.add(AWAR_ACI       (dim), "aci",    dim);
337        cdef.add(AWAR_VALUE_MIN (dim), "min",    dim);
338        cdef.add(AWAR_VALUE_MAX (dim), "max",    dim);
339    }
340}
341
342char *ItemFieldShader::store_config() const {
343    AWT_config_definition cdef;
344    init_config_definition(cdef);
345    return cdef.read();
346}
347
348void ItemFieldShader::load_or_reset_config(const char *cfgstr) {
349    AWT_config_definition cdef;
350    init_config_definition(cdef);
351
352    if (cfgstr) cdef.write(cfgstr);
353    else cdef.reset();
354}
355
356void ItemFieldShader::customize(AW_root *awr) {
357    if (!aw_config) {
358        AW_window_simple *aws = new AW_window_simple;
359        {
360            string wid = GBS_global_string("%s_cfg", get_shader_local_id());
361            aws->init(awr, wid.c_str(), get_description().c_str());
362        }
363
364        aws->auto_space(5, 5);
365        aws->button_length(8);
366
367        int y0 = aws->get_at_yposition();
368
369        aws->callback(AW_POPDOWN);
370        aws->create_button("CLOSE", "CLOSE", "O");
371
372        aws->callback(makeHelpCallback("field_shader.hlp"));
373        aws->create_button("HELP", "HELP");
374
375        aws->at_newline();
376
377        int y = aws->get_at_yposition();                // header-position
378        const char *header[] = { "Use", "Field", "ACI", "min", "max", };
379        const int HCOUNT = ARRAY_ELEMS(header);
380
381        int x[HCOUNT];
382        x[0] = aws->get_at_xposition();
383
384        aws->at_y(y+(y-y0));
385
386        for (int dim = 0; dim<get_max_dimension(); ++dim) {
387            aws->create_toggle(AWAR_DIM_ACTIVE(dim));
388
389            int h = 1;
390            if (!dim) x[h++] = aws->get_at_xposition();
391            FieldSelDef def(AWAR_FIELD(dim), itemtype.gb_main, itemtype.selector, FIELD_FILTER_STRING_READABLE);
392            create_itemfield_selection_button(aws, def, NULL);
393
394            if (!dim) x[h++] = aws->get_at_xposition();
395            aws->create_input_field(AWAR_ACI(dim), 30);
396
397            const int VALCOL = 9;
398            if (!dim) x[h++] = aws->get_at_xposition();
399            aws->create_input_field(AWAR_VALUE_MIN(dim), VALCOL);
400            if (!dim) x[h++] = aws->get_at_xposition();
401            aws->create_input_field(AWAR_VALUE_MAX(dim), VALCOL);
402
403            aws->callback(makeWindowCallback(scan_value_range_cb, this, dim));
404            aws->create_button(GBS_global_string("SCAN%i", dim), "SCAN", 0);
405
406            aws->at_newline();
407        }
408
409        for (int h = 0; h<HCOUNT; ++h) {
410            aws->at(x[h], y);
411            aws->create_button(NULL, header[h], 0);
412        }
413
414        aw_config = aws;
415    }
416    aw_config->activate();
417}
418
419inline const char *make_limit_string(bool use_float, float f, int i) {
420    if (use_float) {
421        // cut off trailing '.0*':
422        char *s = GBS_global_string_copy("%f", f);
423        char *e = strchr(s, 0)-1;
424        while (e>s) {
425            char c = e[0];
426            if (c == '.') {
427                e[0] = 0;
428                break;
429            }
430            if (c != '0') break;
431            *e-- = 0;
432        }
433        const char *cs = GBS_static_string(s);
434        free(s);
435        return cs;
436    }
437    return GBS_global_string("%i", i);
438}
439
440template<typename T>
441class LimitTracker {
442    T min, max;
443
444public:
445    LimitTracker() :
446        min(numeric_limits<T>::max()),
447        max(numeric_limits<T>::min())
448    {}
449
450    void track(T val) {
451        min = std::min(min, val);
452        max = std::max(max, val);
453    }
454    void track(const char *str);
455
456    bool seen() const { return min <= max; }
457    bool is_single_value() const { return !(min<max); }
458
459    T get_min() const { return min; }
460    T get_max() const { return max; }
461
462};
463
464template<> void LimitTracker<int>  ::track(const char *str) { track(atoi(str)); }
465template<> void LimitTracker<float>::track(const char *str) { track(atof(str)); }
466
467
468void ItemFieldShader::scan_value_range_cb(int dim) {
469    AW_root    *awr       = AW_root::SINGLETON;
470    const char *fieldname = awr->awar(AWAR_FIELD(dim))->read_char_pntr();
471    const char *aci       = awr->awar(AWAR_ACI(dim))->read_char_pntr();
472    bool        have_aci  = is_ACI(aci);
473    GB_ERROR    error     = NULL;
474
475    if (strcmp(fieldname, NO_FIELD_SELECTED) == 0) {
476        error = "Select field to scan";
477    }
478    else {
479        LimitTracker<int>   ilimit;
480        LimitTracker<float> flimit;
481
482        bool seen_field      = false;
483        bool is_hierarchical = strchr(fieldname, '/');
484
485        GB_transaction ta(itemtype.gb_main);
486        for (GBDATA *gb_cont = itemtype.get_first_item_container(NULL, QUERY_ALL_ITEMS);
487             gb_cont && !error;
488             gb_cont = itemtype.get_next_item_container(gb_cont, QUERY_ALL_ITEMS))
489        {
490            for (GBDATA *gb_item = itemtype.get_first_item(gb_cont, QUERY_ALL_ITEMS);
491                 gb_item && !error;
492                 gb_item         = itemtype.get_next_item(gb_item, QUERY_ALL_ITEMS))
493            {
494                GBDATA *gb_field = is_hierarchical
495                    ? GB_search(gb_item, fieldname, GB_FIND)
496                    : GB_entry(gb_item, fieldname);
497
498                error = GB_incur_error_if(!gb_field);
499                if (gb_field) {
500                    is_assert(!error);
501                    seen_field = true;
502
503                    if (have_aci) {
504                        char *content = GB_read_as_string(gb_field);
505                        char *applied = GB_command_interpreter(itemtype.gb_main, content, aci, gb_item, NULL);
506
507                        if (!applied) {
508                            error = GB_await_error();
509                        }
510                        else {
511                            ilimit.track(applied);
512                            flimit.track(applied);
513                            free(applied);
514                        }
515                        free(content);
516                    }
517                    else {
518                        GB_TYPES field_type = GB_read_type(gb_field);
519                        switch (field_type) {
520                            case GB_INT: {
521                                int i = GB_read_int(gb_field);
522                                ilimit.track(i);
523                                break;
524                            }
525                            case GB_FLOAT: {
526                                float f = GB_read_float(gb_field);
527                                flimit.track(f);
528                                break;
529                            }
530                            default: {
531                                char *s = GB_read_as_string(gb_field);
532                                ilimit.track(s);
533                                flimit.track(s);
534                                free(s);
535                                break;
536                            }
537                        }
538                    }
539                }
540            }
541        }
542
543        if (seen_field) {
544            // decide whether to use float or int limits
545            bool seen_float = flimit.seen();
546            bool seen_int   = ilimit.seen();
547            is_assert(seen_float || seen_int);
548
549            bool use_float = seen_float && (!seen_int || ilimit.get_max()<flimit.get_max());
550
551            // @@@ avoid duplicate refresh:
552            awr->awar(AWAR_VALUE_MIN(dim))->write_string(make_limit_string(use_float, flimit.get_min(), ilimit.get_min()));
553            awr->awar(AWAR_VALUE_MAX(dim))->write_string(make_limit_string(use_float, flimit.get_max(), ilimit.get_max()));
554
555            bool shading_useless = use_float ? flimit.is_single_value() : ilimit.is_single_value();
556            if (shading_useless) {
557                error = GBS_global_string("Using field '%s' for shading is quite useless", fieldname);
558            }
559        }
560    }
561    aw_message_if(error);
562}
563
564// ------------------
565//      factory:
566
567ShaderPluginPtr makeItemFieldShader(BoundItemSel& itemtype) {
568    return new ItemFieldShader(itemtype);
569}
570
571// --------------------------------------------------------------------------------
572
573#ifdef UNIT_TESTS
574#ifndef TEST_UNIT_H
575#include <test_unit.h>
576#endif
577
578#include <arbdbt.h>
579
580#define TEST_READER_READS(reader,species,expected) TEST_EXPECT_EQUAL(ValueTuple::make((reader).calc_value(species))->inspect(), expected)
581#define TEST_READER_UNDEF(reader,species)          TEST_READER_READS(reader, species, "<undef>")
582#define TEST_MULTI_READS(reader,species,expected)  TEST_EXPECT_EQUAL((reader).calc_value(species)->inspect(), expected)
583#define TEST_MULTI_UNDEF(reader,species)           TEST_MULTI_READS(reader,species, "<undef>")
584
585void TEST_FieldReader() {
586    GB_shell  shell;
587    GBDATA   *gb_main = GB_open("nosuch.arb", "c");
588    TEST_REJECT_NULL(gb_main);
589
590    GBDATA *gb_species, *gb_species2, *gb_species_outofbounds, *gb_species_no_field;
591
592    const char *FIELD_FLOAT  = "float";
593    const char *FIELD_INT    = "int";
594    const char *FIELD_STRING = "string";
595    {
596        GB_transaction ta(gb_main);
597
598        gb_species             = GBT_find_or_create_species(gb_main, "test");  TEST_REJECT_NULL(gb_species);
599        gb_species2            = GBT_find_or_create_species(gb_main, "other"); TEST_REJECT_NULL(gb_species2);
600        gb_species_no_field    = GBT_find_or_create_species(gb_main, "empty"); TEST_REJECT_NULL(gb_species_no_field);
601        gb_species_outofbounds = GBT_find_or_create_species(gb_main, "outer"); TEST_REJECT_NULL(gb_species_outofbounds);
602
603        GBDATA *gb_field;
604        gb_field = GB_searchOrCreate_float (gb_species, FIELD_FLOAT,  0.25);        TEST_REJECT_NULL(gb_field);
605        gb_field = GB_searchOrCreate_int   (gb_species, FIELD_INT,    50);          TEST_REJECT_NULL(gb_field);
606        gb_field = GB_searchOrCreate_string(gb_species, FIELD_STRING, "200 units"); TEST_REJECT_NULL(gb_field);
607
608        gb_field = GB_searchOrCreate_float (gb_species2, FIELD_FLOAT,  0.9);     TEST_REJECT_NULL(gb_field);
609        gb_field = GB_searchOrCreate_int   (gb_species2, FIELD_INT,    99);      TEST_REJECT_NULL(gb_field);
610        gb_field = GB_searchOrCreate_string(gb_species2, FIELD_STRING, "175.9"); TEST_REJECT_NULL(gb_field);
611
612        gb_field = GB_searchOrCreate_float (gb_species_outofbounds, FIELD_FLOAT,  1.5);          TEST_REJECT_NULL(gb_field);
613        gb_field = GB_searchOrCreate_int   (gb_species_outofbounds, FIELD_INT,    9999);         TEST_REJECT_NULL(gb_field);
614        gb_field = GB_searchOrCreate_string(gb_species_outofbounds, FIELD_STRING, "-12345.678"); TEST_REJECT_NULL(gb_field);
615    }
616
617    FieldReader nullReader;
618    FieldReader missingReader("missing",    NULL, 0,   1);
619    FieldReader floatReader  (FIELD_FLOAT,  NULL, 1,   0); // reverse value-range!
620    FieldReader intReader    (FIELD_INT,    NULL, 0,   100);
621    FieldReader stringReader (FIELD_STRING, NULL, 100, 250);
622
623    FieldReader aciReader(FIELD_STRING, "|contains(unit)", 0, 1);
624
625    TEST_REJECT(nullReader.may_read());
626    TEST_EXPECT(missingReader.may_read());
627    TEST_EXPECT(stringReader.may_read());
628    TEST_EXPECT(aciReader.may_read());
629
630    {
631        GB_transaction ta(gb_main);
632
633        // expect undef if no species - no matter what reader is used (eg. zombie in tree)
634        TEST_READER_UNDEF(nullReader,    NULL);
635        TEST_READER_UNDEF(missingReader, NULL);
636        TEST_READER_UNDEF(floatReader,   NULL);
637        TEST_READER_UNDEF(intReader,     NULL);
638        TEST_READER_UNDEF(stringReader,  NULL);
639        TEST_READER_UNDEF(aciReader,     NULL);
640
641        TEST_READER_UNDEF(nullReader,    gb_species); // null reader always undef
642        TEST_READER_UNDEF(missingReader, gb_species); // expect undef if field is missing
643
644        TEST_READER_READS(floatReader,   gb_species, "(0.750)");
645        TEST_READER_READS(intReader,     gb_species, "(0.500)");
646        TEST_READER_READS(stringReader,  gb_species, "(0.667)");
647        TEST_READER_READS(aciReader,     gb_species, "(5.000)"); // = position
648
649        TEST_READER_READS(floatReader,   gb_species2, "(0.100)");
650        TEST_READER_READS(intReader,     gb_species2, "(0.990)");
651        TEST_READER_READS(stringReader,  gb_species2, "(0.506)"); // 175 would be mid-range, 175.9 is a little bit above
652        TEST_READER_READS(aciReader,     gb_species2, "(0.000)");
653
654        // if values are outside of value-range -> they are scaled to range-size, but not bounded:
655        TEST_READER_READS(floatReader,   gb_species_outofbounds, "(-0.500)");
656        TEST_READER_READS(intReader,     gb_species_outofbounds, "(99.990)");
657        TEST_READER_READS(stringReader,  gb_species_outofbounds, "(-82.971)");
658        TEST_READER_READS(aciReader,     gb_species_outofbounds, "(0.000)");
659
660        TEST_READER_UNDEF(floatReader,   gb_species_no_field); // species is missing all fields -> always undef
661        TEST_READER_UNDEF(intReader,     gb_species_no_field);
662        TEST_READER_UNDEF(stringReader,  gb_species_no_field);
663        TEST_READER_UNDEF(aciReader,     gb_species_no_field);
664    }
665
666    MultiFieldReader multi;        TEST_EXPECT_EQUAL(multi.get_dimension(), 0);
667    multi.add_reader(nullReader);  TEST_EXPECT_EQUAL(multi.get_dimension(), 0);
668    multi.add_reader(floatReader); TEST_EXPECT_EQUAL(multi.get_dimension(), 1);
669
670    {
671        GB_transaction ta(gb_main);
672       
673        // only floatReader added yet -> should behave like floatReader did above:
674        TEST_MULTI_READS(multi, gb_species,             "(0.750)");
675        TEST_MULTI_READS(multi, gb_species2,            "(0.100)");
676        TEST_MULTI_READS(multi, gb_species_outofbounds, "(-0.500)");
677        TEST_MULTI_UNDEF(multi, gb_species_no_field);
678    }
679
680    GB_close(gb_main);
681}
682
683#endif // UNIT_TESTS
684
685// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.