filtering observable using observable condition

Fuse 0.12.0.6252, Mac, Local Preview

Made a small stand-alone project to replicate the (issue?) of my larger app crashing on iOS preview, because it simply runs out of memory.

Here’s a question / how-to / have-no-idea-what-am-I-doing-wrong:

When following @hasselknippe’s example for observable filtering conditions, I noticed that my debug gets printed twice: https://www.dropbox.com/s/829kgo5pd4mdvkg/Screenshot%202016-04-14%2016.41.00.png?dl=0 So I’m wondering if the approach of nested Observable functions itself is valid, or if I’ve stumbled upon a completely different issue.

For a much larger Observable, I assume it’s exactly what triggers the memory usage problem, since I believe that the number of Observables created increses multiple times more than needed.

Using the example below is as simple as entering “inte” or “inta” to see how the items change and trigger the console.log(). Here’s the test code:

<App Theme="Basic">
    <JavaScript>
    var Observable = require("FuseJS/Observable");
    var search_text = Observable('inta');

    function stringContainsString(against, match) {
        var pattern = new RegExp(match, 'i');
        return pattern.test(against);
    };

    function Obj(text) {
        return {'text':text};
    }

    var data = Observable();
    data.add(new Obj('test inta 1'));
    data.add(new Obj('test inta 2'));
    data.add(new Obj('test inta 3'));
    data.add(new Obj('test inte 1'));
    data.add(new Obj('test inte 2'));
    data.add(new Obj('test inte 3'));

    var subset = data.where(function(e) {
        return Observable(function() {
            console.log('checking: ' + JSON.stringify(e));
            return stringContainsString(e.text, search_text.value);
        });
    });


    module.exports = {
        'subset': subset,
        'search_text': search_text
    };
    </JavaScript>

    <ClientPanel>
        <StackPanel Background="#333">
            <Panel>
                <TextInput Value="{search_text}" TextColor="#fff" />
            </Panel>
            <StackPanel>
                <Each Items="{subset}">
                    <Text TextColor="#fff" Value="{text}" Alignment="Center" Margin="3" />
                </Each>
            </StackPanel>
        </StackPanel>
    </ClientPanel>
</App>

Hi! Thanks for reporting this. We’ll investigate.

Hi!

The fact that the console.log is printed twice isn’t necessarily a bug. However, the fact that you are running out of memory for only a handfull of items definitely point to a leak somewhere. Would you be able to supply us with the full project that is actually having this memory problem?

Thanks

Thanks guys, no full project needed. Here’s a valid test case, there’s all you need.

What you do is basically keep entering and deleting 3-letter combos in the search box. Anything that matches something from the list (hint: the content is in latin). Do it over and over again, and watch how the XCode mem usage dances gracefully to hell (read: over 500MB when it dies): https://www.dropbox.com/s/xr8disg40ywk48n/Screenshot%202016-04-15%2020.09.56.png?dl=0

<App Theme="Basic">
    <JavaScript>
    var Observable = require("FuseJS/Observable");
    var search_text = Observable('provident');

    function stringContainsString(against, match) {
        var pattern = new RegExp(match, 'i');
        return pattern.test(against);
    };

    function Obj(text) {
        return {'text':text};
    }

    var data = Observable();
    var subset = data.where(function(e) {
        return Observable(function() {
            //console.log('checking: ' + JSON.stringify(e));
            return stringContainsString(e.text, search_text.value);
        });
    }).map(
        function(item, index) {
            return {'item':item,'index':index};
        }
    ).where(
        function(obj) {
            return obj.index < 5;
        }
    ).map(
        function(obj) {
            return obj.item;
        }
    );

    function get_data() {
        var url = 'http://jsonplaceholder.typicode.com/comments';
        fetch(url)
        .then(function(response) {
            return response.json();
        })
        .then(function(responseObject) {
            // got the data, pass it out!
            data_retrieved(responseObject);
        }).catch(function (err) {
            console.log('network request error: ' + err);
        });
    }

    function data_retrieved(apidata) {
        //console.log(JSON.stringify(apidata));
        for (var i in apidata) {
            data.add({'text':apidata[i].name});
        }
    }

    // init
    get_data();

    module.exports = {
        'subset': subset,
        'search_text': search_text
    };
    </JavaScript>

    <ClientPanel>
        <StackPanel Background="#333">
            <Panel>
                <TextInput Value="{search_text}" TextColor="#fff" />
            </Panel>
            <StackPanel>
                <Each Items="{subset}">
                    <Text TextColor="#fff" Value="{text}" Alignment="Center" Margin="3" />
                </Each>
            </StackPanel>
        </StackPanel>
    </ClientPanel>
</App>

I should add that the 3-letter combos you enter should all be different. Do not repeat them, since the effect is best seen when the strings change.

What usually happens is, when you delete the combo you had AND enter the first (different) new letter, the app kind of freezes for a short moment. Might tell you something.

This is an alternative approach that seems to be a little lighter on memory:

<App Theme="Basic">
    <JavaScript>
    var Observable = require("FuseJS/Observable");
    var search_text = Observable('inta');

    function stringContainsString(against, match) {
        var pattern = new RegExp(match, 'i');
        return pattern.test(against);
    };

    function Obj(text) {
        return {'text':text};
    }

    var data = Observable();
    data.add(new Obj('test inta 1'));
    data.add(new Obj('test inta 2'));
    data.add(new Obj('test inta 3'));
    data.add(new Obj('test inte 1'));
    data.add(new Obj('test inte 2'));
    data.add(new Obj('test inte 3'));

    var subset = search_text.map (function (s) {
        console.log ('Search text: ' + s)
        return data.where(function (e) {             
            var contains = stringContainsString(e.text, s);            
            return contains;
        });        
    }).inner();

    module.exports = {
        'subset': subset,
        'search_text': search_text
    };
    </JavaScript>

    <ClientPanel>
        <StackPanel Background="#333">
            <Panel>
                <TextInput Value="{search_text}" TextColor="#fff" />
            </Panel>
            <StackPanel>
                <Each Items="{subset}">               
                    <Text TextColor="#fff" Value="{text}" Alignment="Center" Margin="3" />
                </Each>
            </StackPanel>
        </StackPanel>
    </ClientPanel>
</App>

It generates a subset by mapping over the search string and returning a subset with a where-projection.