Skip to content

Commit 0d1ad26

Browse files
committed
Add TextSort example from snapchat
1 parent e9bbff6 commit 0d1ad26

23 files changed

+637
-2
lines changed

.github/workflows/build.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,26 @@ jobs:
123123
working-directory: build/test-suite
124124
run: ctest -C Release -V
125125

126+
build-on-ubuntu-for-wasm:
127+
runs-on: ubuntu-latest
128+
steps:
129+
- uses: actions/checkout@v2
130+
- uses: asdf-vm/actions/install@v1
131+
- name: Install dependencies
132+
uses: py-actions/py-dependency-install@v3
133+
with:
134+
path: "requirements.txt"
135+
- name: Setup EMSDK
136+
uses: mymindstorm/setup-emsdk@v11
137+
with:
138+
version: 3.1.44
139+
- name: Report cmake version
140+
run: cmake --version
141+
- name: Configure cmake
142+
run: cmake -S . -B build -DDJINNI_WITH_WASM=ON
143+
- name: Build release
144+
run: cmake --build build --parallel $(nproc) --config Release
145+
126146

127147
build-on-windows-for-cppcli:
128148
runs-on: windows-latest

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ project(djinni_support_lib CXX ${PROJECT_LANGUAGES})
1010

1111
include(GNUInstallDirs)
1212

13+
include(cmake/Djinni.cmake)
14+
1315
set(SRC_SHARED
1416
"djinni/djinni_common.hpp"
1517
"djinni/proxy_cache_interface.hpp"
@@ -211,5 +213,6 @@ option(DJINNI_BUILD_TESTING "Build tests" ON)
211213

212214
include(CTest)
213215
if (BUILD_TESTING AND DJINNI_BUILD_TESTING)
216+
add_subdirectory(examples)
214217
add_subdirectory(test-suite)
215218
endif()

test-suite/Djinni.cmake renamed to cmake/Djinni.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ function(add_djinni_target)
450450

451451
resolve_djinni_outputs(COMMAND "${DJINNI_TS_GENERATION_COMMAND}" RESULT TS_OUT_FILES)
452452

453+
message(STATUS "Output: ${TS_OUT_FILES}, Depends: ${DJINNI_INPUTS}, Command: ${DJINNI_TS_GENERATION_COMMAND}")
453454
add_custom_command(
454455
OUTPUT ${TS_OUT_FILES}
455456
DEPENDS ${DJINNI_INPUTS}

examples/CMakeLists.txt

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
cmake_minimum_required(VERSION 3.6.0)
2+
3+
set(CMAKE_CXX_STANDARD 17)
4+
5+
if(APPLE)
6+
set(MACOSX_RPATH TRUE)
7+
set(CMAKE_MACOSX_RPATH ${CMAKE_CURRENT_BINARY_DIR})
8+
endif()
9+
10+
enable_testing()
11+
12+
add_djinni_target(DjinniTextSort
13+
IDL "example.djinni"
14+
# IDL_INCLUDE_PATH "djinni/vendor"
15+
CPP_OUT "${CMAKE_CURRENT_BINARY_DIR}/generated-src/cpp"
16+
CPP_NAMESPACE "textsort"
17+
IDENT_CPP_ENUM_TYPE "foo_bar"
18+
CPP_EXTENDED_RECORD_INCLUDE_PREFIX "handwritten-src/cpp/"
19+
CPP_ENUM_HASH_WORKAROUND
20+
JAVA_OUT "${CMAKE_CURRENT_BINARY_DIR}/generated-src/java"
21+
JAVA_PACKAGE "com.dropbox.textsort"
22+
JAVA_GENERATE_INTERFACES
23+
JAVA_IMPLEMENT_ANDROID_OS_PARCELABLE
24+
JAVA_NULLABLE_ANNOTATION "javax.annotation.CheckForNull"
25+
JAVA_NONNULL_ANNOTATION "javax.annotation.Nonnull"
26+
IDENT_JAVA_FIELD "mFooBar"
27+
JNI_OUT "${CMAKE_CURRENT_BINARY_DIR}/generated-src/jni"
28+
IDENT_JNI_CLASS "NativeFooBar"
29+
IDENT_JNI_FILE "NativeFooBar"
30+
OBJC_OUT "${CMAKE_CURRENT_BINARY_DIR}/generated-src/objc"
31+
OBJC_TYPE_PREFIX "DB"
32+
OBJCPP_OUT "${CMAKE_CURRENT_BINARY_DIR}/generated-src/objc"
33+
CPPCLI_OUT "${CMAKE_CURRENT_BINARY_DIR}/generated-src/cppcli"
34+
CPPCLI_NAMESPACE "Djinni::TestSuite"
35+
CPPCLI_INCLUDE_CPP_PREFIX "cpp/"
36+
WASM_OUT "${CMAKE_CURRENT_BINARY_DIR}/generated-src/wasm"
37+
TS_OUT "${CMAKE_CURRENT_BINARY_DIR}/generated-src/ts"
38+
TS_MODULE "example"
39+
YAML_OUT "${CMAKE_CURRENT_BINARY_DIR}/generated-src/yaml"
40+
YAML_PREFIX "test_"
41+
42+
CPP_OUT_FILES CPP_GENERATED_SRCS
43+
JAVA_OUT_FILES JAVA_GENERATED_SRCS
44+
JNI_OUT_FILES JNI_GENERATED_SRCS
45+
OBJC_OUT_FILES OBJC_GENERATED_SRCS
46+
OBJCPP_OUT_FILES OBJCPP_GENERATED_SRCS
47+
CPPCLI_OUT_FILES CPPCLI_GENERATED_SRCS
48+
WASM_OUT_FILES WASM_GENERATED_SRCS
49+
TS_OUT_FILES TS_GENERATED_SRCS
50+
YAML_OUT_FILE YAML_GENERATED_SRCS
51+
)
52+
53+
if(DJINNI_WITH_WASM)
54+
set(DjinniTextsortWasm "textsort-wasm")
55+
56+
set(CPP_HANDWRITTEN_SRCS
57+
${CMAKE_CURRENT_SOURCE_DIR}/handwritten-src/cpp/sort_items_impl.cpp
58+
)
59+
60+
add_executable(${DjinniTextsortWasm}
61+
${CPP_HANDWRITTEN_SRCS}
62+
${CPP_GENERATED_SRCS}
63+
${WASM_GENERATED_SRCS}
64+
${TS_GENERATED_SRCS}
65+
)
66+
target_include_directories(${DjinniTextsortWasm} PUBLIC
67+
${CMAKE_SOURCE_DIR}
68+
${CMAKE_CURRENT_SOURCE_DIR}/handwritten-src/cpp
69+
${CMAKE_CURRENT_BINARY_DIR}/generated-src/cpp
70+
${CMAKE_CURRENT_BINARY_DIR}/generated-src/wasm
71+
)
72+
73+
set(WASM_CC_FLAGS "-s MAIN_MODULE=1 -fexceptions")
74+
message(WARNING ${WASM_CC_FLAGS})
75+
set_target_properties(${DjinniTextsortWasm} PROPERTIES COMPILE_FLAGS ${WASM_CC_FLAGS})
76+
77+
string(JOIN " " EMSCRIPTEN_LINK_OPTIONS
78+
"-s WASM=1"
79+
"--bind" # Compiles the source code using the Embind bindings to connect C/C++ and JavaScript
80+
"-s MALLOC=emmalloc" # Switch to using the much smaller implementation
81+
"-s MODULARIZE=1" # Allows us to manually invoke the initialization of wasm
82+
"-s WASM_BIGINT=1" # We need to pass int64_t
83+
)
84+
85+
set(WASM_LINK_FLAGS "${EMSCRIPTEN_LINK_OPTIONS} -fexceptions")
86+
message(WARNING ${WASM_LINK_FLAGS})
87+
set_target_properties(${DjinniTextsortWasm} PROPERTIES LINK_FLAGS ${WASM_LINK_FLAGS})
88+
set(CMAKE_EXECUTABLE_SUFFIX ".js")
89+
90+
target_link_libraries(${DjinniTextsortWasm} PUBLIC djinni-support-lib::djinni-support-lib embind)
91+
92+
add_custom_target(DjinniTextsortWasmCopy)
93+
file(GLOB DjinniTextsortWasmResources ${CMAKE_CURRENT_SOURCE_DIR}/ts/*)
94+
foreach(ResourceFile ${DjinniTextsortWasmResources})
95+
add_custom_command(TARGET ${DjinniTextsortWasm} PRE_BUILD
96+
COMMAND ${CMAKE_COMMAND} -E
97+
copy ${ResourceFile} $<TARGET_FILE_DIR:${DjinniTextsortWasm}>)
98+
endforeach()
99+
add_dependencies(${DjinniTextsortWasm} DjinniTextsortWasmCopy)
100+
101+
# add_test(NAME DjinniWasmTestTests COMMAND bash -c "${CMAKE_CURRENT_BINARY_DIR}/run.sh")
102+
103+
endif()

examples/example.djinni

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
item_list = record {
2+
items: list<string>;
3+
}
4+
5+
sort_order = enum {
6+
ascending;
7+
descending;
8+
random;
9+
}
10+
11+
sort_items = interface +c {
12+
# For the iOS / Android demo
13+
sort(order: sort_order, items: item_list);
14+
static create_with_listener(listener: textbox_listener): sort_items;
15+
16+
# For the localhost / command-line demo
17+
static run_sort(items: item_list): item_list;
18+
}
19+
20+
textbox_listener = interface +j +o +w {
21+
update(items: item_list);
22+
}

examples/example.yaml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# This is an example YAML file for being imported in other projects.
2+
# It holds all necessary information for Djinni to integrate foreign types into the generated code.
3+
# All fields are mandatory.
4+
---
5+
# The name to refer to this type in other .djinni files.
6+
# It must be unique in the entire Djinni run, so you should pick a unique prefix for your framework/library.
7+
name: mylib_record1
8+
# Specifies what kind of type this is.
9+
# Supports the same syntax as is used to declare types in .djinni files.
10+
# Examples: 'interface +c', 'record deriving(eq, or)', 'enum', 'flags', 'interface +j +o'
11+
# This determines how Djinni integrates this type into function parameters, fields or return types and operators.
12+
typedef: 'record +c deriving(eq, ord)'
13+
# The (potentially empty) list of required type parameters.
14+
params: [type_param1, type_param2]
15+
# This string is stripped from the value specified under "name" to ensure Djinni is referencing the correct typename in code.
16+
# May be an empty string if you don't have a prefix (bad!)
17+
prefix: 'mylib'
18+
cpp:
19+
# The name of this type in C++ (without template arguments). Should be fully qualified.
20+
typename: '::mylib::Record1'
21+
# The header required in C++ to use your type. Must include "" or <>.
22+
header: '"MyLib/Record1.hpp"'
23+
# Only used for "record" types: determines whether it should be passed by-value in C++.
24+
# If this is false it is always passed as const&
25+
byValue: false
26+
objc:
27+
# The name of this type in Objective-C.
28+
typename: 'MLBRecord1'
29+
# The header required in Objective-C to use your type.
30+
header: '"MLB/MLBRecord1.h"'
31+
# Only used for "record" types: determines the type used when boxing the record is required.
32+
# Should not contain the pointer asterisk "*", protocols are not supported.
33+
# This files is the same as "typename" most of the time as records are typically NSObjects and require no special boxing.
34+
# However, some may not, for example NSTimeInterval is boxed to NSNumber.
35+
boxed: 'MLBRecord1'
36+
# Specifies whether the unboxed type is a pointer.
37+
pointer: true
38+
# If the type is a "record" and has "eq" deriving then this string must not be empty.
39+
# It declares a well-formed expression with a single "%s" format placeholder replaced with the variable for which the hash code is needed
40+
hash: '%s.hash'
41+
objcpp:
42+
# The fully qualified name of the class containing the toCpp/fromCpp methods.
43+
translator: '::mylib::djinni::objc::Record1'
44+
# Where to find the translator class.
45+
header: '"mylib/djinni/objc/Record1.hpp"'
46+
java:
47+
# The name of the (fully qualified) Java type to be used.
48+
typename: 'com.example.mylib.Record1'
49+
# Only used for "record" types: determines the type used when boxing the record is required.
50+
# This field is the same as "typename" most of the time as records are typically Objects and require no special boxing.
51+
# However maybe your record has a dedicated boxed type and this field allows you to control that.
52+
boxed: 'com.example.mylib.Record1'
53+
# If this is true "typename" is an Object reference (and not a builtin).
54+
reference: true
55+
# Controls whether the type parameters as specified in "params" are forwarded as generics to Java.
56+
# This is useful when templates are only used in C++ (e.g. std::chrono::duration<rep, period> versus java.time.Duration)
57+
# This should be true by default (even if your type has no parameters) and only set to false if required
58+
generic: true
59+
# If the type is a "record" and has "eq" deriving then this string must not be empty.
60+
# It declares a well-formed expression with a single "%s" format placeholder replaced with the variable for which the hash code is needed
61+
hash: '%s.hashCode()'
62+
jni:
63+
# The fully qualified name of the class containing the toCpp/fromCpp methods.
64+
translator: '::mylib::djinni::jni::Record1'
65+
# Where to find the translator class.
66+
header: '"mylib/djinni/jni/Record1.hpp"'
67+
# The type used for representations in JNI (jobject, jint, jbyteArray, etc)
68+
typename: jobject
69+
# The mangled type signature of your type to be found by JNI.
70+
# See the JNI docs for its format
71+
typeSignature: 'Lcom/example/mylib/Record1;'
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include <algorithm>
2+
#include <random>
3+
4+
#include "item_list.hpp"
5+
#include "sort_order.hpp"
6+
7+
#include "sort_items_impl.hpp"
8+
9+
namespace textsort {
10+
11+
std::shared_ptr<SortItems> SortItems::create_with_listener(const std::shared_ptr<TextboxListener>& listener) {
12+
return std::make_shared<SortItemsImpl>(listener);
13+
}
14+
15+
SortItemsImpl::SortItemsImpl (const std::shared_ptr<TextboxListener> & listener) {
16+
this->m_listener = listener;
17+
}
18+
19+
void SortItemsImpl::sort(sort_order order, const ItemList & items) {
20+
auto lines = items.items;
21+
switch (order) {
22+
case sort_order::ASCENDING: {
23+
std::sort(lines.begin(), lines.end(), std::less<std::string>());
24+
break;
25+
}
26+
case sort_order::DESCENDING: {
27+
std::sort(lines.begin(), lines.end(), std::greater<std::string>());
28+
break;
29+
}
30+
case sort_order::RANDOM: {
31+
std::shuffle(lines.begin(), lines.end(), std::default_random_engine{});
32+
break;
33+
}
34+
}
35+
36+
// Pass result to client interface
37+
this->m_listener->update(ItemList(lines));
38+
}
39+
40+
ItemList SortItems::run_sort(const ItemList & items) {
41+
auto lines = items.items;
42+
std::sort(lines.begin(), lines.end(), std::less<std::string>());
43+
return ItemList(lines);
44+
}
45+
46+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#pragma once
2+
3+
#include "sort_items.hpp"
4+
#include "textbox_listener.hpp"
5+
6+
namespace textsort {
7+
8+
class SortItemsImpl : public SortItems {
9+
10+
public:
11+
SortItemsImpl(const std::shared_ptr<TextboxListener> & listener);
12+
virtual void sort(sort_order order, const ItemList & items) override;
13+
14+
private:
15+
std::shared_ptr<TextboxListener> m_listener;
16+
17+
};
18+
19+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.dropbox.textsort;
2+
3+
import android.app.Activity;
4+
import android.os.Bundle;
5+
import android.view.View;
6+
import android.widget.EditText;
7+
8+
import java.util.ArrayList;
9+
import java.util.Arrays;
10+
11+
public class MainActivity extends Activity {
12+
13+
private EditText text;
14+
private SortItems sortItemsInterface;
15+
private TextboxListener textboxListener;
16+
17+
static {
18+
System.loadLibrary("android-app");
19+
}
20+
21+
@Override
22+
protected void onCreate(Bundle savedInstanceState) {
23+
super.onCreate(savedInstanceState);
24+
setContentView(R.layout.activity_main);
25+
26+
text = (EditText) findViewById(R.id.editText);
27+
textboxListener = new TextboxListenerImpl(text);
28+
// Call JNI to initiate the SortItems object from the given textboxListener and translate to Java
29+
sortItemsInterface = SortItems.createWithListener(textboxListener);
30+
}
31+
32+
public void sort(SortOrder order) {
33+
String str = text.getText().toString();
34+
ArrayList<String> items = new ArrayList<String>(Arrays.asList(str.split("\n")));
35+
ItemList itemList = new ItemList(items);
36+
sortItemsInterface.sort(order, itemList);
37+
}
38+
39+
public void sortAsc(View view) {
40+
this.sort(SortOrder.ASCENDING);
41+
}
42+
43+
public void sortDesc(View view) {
44+
this.sort(SortOrder.DESCENDING);
45+
}
46+
47+
public void sortRandom(View view) {
48+
this.sort(SortOrder.RANDOM);
49+
}
50+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.dropbox.textsort;
2+
3+
import android.widget.EditText;
4+
5+
import java.util.ArrayList;
6+
7+
public class TextboxListenerImpl extends TextboxListener {
8+
9+
private EditText mTextArea;
10+
11+
public TextboxListenerImpl(EditText textArea) {
12+
this.mTextArea = textArea;
13+
}
14+
15+
@Override
16+
public void update(ItemList items) {
17+
ArrayList<String> list = items.getItems();
18+
StringBuilder builder = new StringBuilder();
19+
for (String str : list) {
20+
builder.append(str);
21+
builder.append("\n");
22+
}
23+
mTextArea.setText(builder);
24+
}
25+
}

0 commit comments

Comments
 (0)