UnityStream Filtering Tutorial for Unity V4

 

These docs are for PubNub 4.x for Unity which is our latest and greatest! For the docs of the older versions of the SDK, please check PubNub 3.x for Unity.

If you have questions about the PubNub for Unity SDK, please contact us at support@pubnub.com.

Stream Filter allows a subscriber to apply a filter to only receive messages that satisfy the conditions of the filter. The message filter is set by the subscribing client(s) but it is applied on the server side thus preventing unwanted messages (those that do not meet the conditions of the filter) from reaching the subscriber.

Stream Filters are implemented with two components: meta dictionary on publish and filter expression on subscribe.

 

Filters are applied to all channels that the client is subscribed to. When messages are encrypted (using crypto key when initializing PubNub), the meta dictionary is plain text, so that the PubNub Network can properly apply the filters as required. It is important to only include information that is not confidential or otherwise requiring encryption.

To use Stream filtering, it is important to include filtering information when publishing a new message. the meta field is not included in the payload itself but allows other clients to filter on the supplied information.

To include meta, prepare the data dictionary and pass it to the publishing function as the following example.

Dictionary<string, object> meta = new Dictionary<string, object>();
meta.Add("my", "meta");
meta.Add("name", "PubNub");

pubnub.Publish()
    .Channel("ch1")
    .Message("hello")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });

With the meta information being published on publish, we can now leverage the stream filtering to omit message that are not important for a particular client.

PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.SubscribeKey = "my_subkey";
pnConfiguration.FilterExpression = "filter == 'expression'";
 
pubnub = new PubNub(pnConfiguration);

Setting a filter applies to all channels that you will subscribe to from that particular client. This client filter excludes messages that have this subscriber's UUID set at the sender's UUID:

PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.SubscribeKey = "my_subkey";
pnConfiguration.FilterExpression = string.Format("uuid != '%s'", pnConfiguration.UUID);
  
pubnub = new PubNub(pnConfiguration);

When publishing messages, you need to include the sender's UUID if you want the subscriber side client filter to work:

Dictionary<string, object> meta = new Dictionary<string, object>();
meta.Add("uuid", pnConfiguration.UUID);

pubnub.Publish()
    .Channel("ch1")
    .Message("hello")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });

For the second example, we are going to publish the locale for which the published message was generated and allow the client to only receive languages in a specific language.

When publishing, the client uses the meta field to publish:

Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("language", "english");

pubnub.Publish()
    .Channel("ch1")
    .Message("hi!")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });

On the subscriber side, we specify the filter expression to make sure only english messages will come to our subscriber.

PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.SubscribeKey = "my_subkey";
pnConfiguration.FilterExpression = "language != 'english'";
  
pubnub = new PubNub(pnConfiguration);

Any messages that do not have language=english in the meta will not arrive to the client. For example, the following publish will not be received.

Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("language", "french");

pubnub.Publish()
    .Channel("ch1")
    .Message("Bonjour!")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });

We can improve our second example with support for multiple languages. As our second example we are going to publish in english, french and spanish but receive only french and spanish.

Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("language", "english");

pubnub.Publish()
    .Channel("ch1")
    .Message("Hi!")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });
Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("language", "french");

pubnub.Publish()
    .Channel("ch1")
    .Message("Bonjour!")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });
Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("language", "spanish");

pubnub.Publish()
    .Channel("ch1")
    .Message("Hola!")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });

On the subscribe side, we are going to use the contains operator to support multiple languages.

PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.SubscribeKey = "my_subkey";
pnConfiguration.FilterExpression = "('french', 'spanish') contains language";
  
pubnub = new PubNub(pnConfiguration);

For the fourth example, we would like to subscribe to all languages except spanish. We begin by publishing similar to example two.

Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("language", "english");

pubnub.Publish()
    .Channel("ch1")
    .Message("Hi!")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });
Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("language", "french");

pubnub.Publish()
    .Channel("ch1")
    .Message("Bonjour!")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });
Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("language", "spanish");

pubnub.Publish()
    .Channel("ch1")
    .Message("Hola!")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });

On the subscribe side, we are going to use the != operator to reject messages written in spanish.

PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.SubscribeKey = "my_subkey";
pnConfiguration.FilterExpression = "language != 'spanish'";
  
pubnub = new PubNub(pnConfiguration);
Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("price", "99.75");
meta.Add("channel", "AAPL");

pubnub.Publish()
    .Channel("AAPL")
    .Message("99.75")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });
Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("price", "100.10");
meta.Add("channel", "AAPL");

pubnub.Publish()
    .Channel("AAPL")
    .Message("100.10")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });
Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("price", "15.50");
meta.Add("channel", "GOOG");

pubnub.Publish()
    .Channel("GOOG")
    .Message("15.50")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });
Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("price", "14.95");
meta.Add("channel", "GOOG");

pubnub.Publish()
    .Channel("GOOG")
    .Message("14.95")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });
Client filter would be applied to all channels by default but you could do something like this:
PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.SubscribeKey = "my_subkey";
pnConfiguration.FilterExpression = "(price > 100.00 && channel == 'AAPL') || (price < 15.00 && channel == 'GOOG')";
  
pubnub = new PubNub(pnConfiguration);

Arithmetic operations are useful when subscribing to stream readings such as temperature. In our example, we are going to publish temperature and only subscribe to events when the temperature is greater than the limit.

Dictionary<string, string> meta = new Dictionary<string, string>();
meta.Add("temperature", "60");

pubnub.Publish()
    .Channel("ch1")
    .Message("Hi!")
    .Meta(meta)
    .Async((result, status) => {
        if (!status.Error) {
            Debug.Log(string.Format("DateTime {0}, In Publish Example, Timetoken: {1}", DateTime.UtcNow , result.Timetoken));
        } else {
            Debug.Log(status.Error);
            Debug.Log(status.ErrorData.Info);
        }
    });

On the subscriber side, we modify the expression to use the > operator

PNConfiguration pnConfiguration = new PNConfiguration();
pnConfiguration.SubscribeKey = "my_subkey";
pnConfiguration.FilterExpression = "temperature > 50";
  
pubnub = new PubNub(pnConfiguration);

The filtering language is extensive and supports advanced use-cases:

compound_expression is root

<compound_expression>         ::=   <expression> | <expression> <binary_logical_op> <expression>
<binary_logical_op>           ::=   && | ||          
<expression>                  ::=   (<expression>) | <operand> <comparison_operator> <operand> | <unary_logical_op> <operand>
<numeric_comparison_operator> ::=   {==, !=, <, >, <=, >=}
<string_comparison_operator>  ::=   {contains, like}
<unary_logical_op>            ::=   !
<operand>                     ::=   (<operand>) | <unary_op> <operand> | <literals> <binary_op> <literals>
<unary_op>                    ::=   ~
<binary_op>                   ::=   |, &, ^, +, -, /, \*
string == 'match'                   => exact match
string LIKE 'match*'				=> LIKE operator: asterisk wildcarding, case insensitive
string LIKE 'match\*'				=> LIKE operator: literal match with string containing asterisk character
('Anne','anna','Ann') like 'ann*'	=> LIKE operator: any of the three set members would be a sufficient match

('a','b','c') CONTAINS string		=> Compare against a list of values
otherstring CONTAINS string			=> Check for a substring match

(3,5,9) contains numValue			=> compare number to a list of values
!((3,5,9) contains numValue)		=> Negation

string contains numValue            => str(numValue) in string

numValue > (numA + numB - numC)	   => compare number to an arithmetic expression
(numA ^ numB) != (numValue * 10)	=> compare 2 expressions
(~numA / numB) <= numValue