Use multiple actors
In this tutorial, you are going to create a project with multiple actors.
Currently, you can only define one actor in a Motoko file and a single actor is always compiled to a single canister.
You can, however, create projects that have multiple actors and can build multiple canisters from the same dfx.json configuration file.
For this tutorial, you are going to create separate program files for three actors in the same project. This project defines the following unrelated actors:
-
The
assistantactor provides functions to add and show tasks in a to-do list.For simplicity, the code sample for this tutorial only includes the functions to add to-do items and to show the current list of to-do items that have been added. A more complete version of this program—with additional functions for marking items as complete and removing items from the list—is available in the examples repository as Simple to-do checklist.
-
The
rock_paper_scissorsactor provides a function for determining a winner in a hard-coded rock-paper-scissors contest.This code sample illustrates the basic use of
switchandcasein a Motoko program with hard-coded players and choices. -
The
daemonactor provides mock functions for starting and stopping a daemon.This code sample simply assigns a variable and prints messages for demonstration purposes.
Before you begin
Before starting the tutorial, verify the following:
-
You have downloaded and installed the DFINITY Canister SDK package as described in Download and install.
-
You have stopped any Internet Computer network processes running on the local computer.
This tutorial takes approximately 20 minutes to complete.
Create a new project
To create a new project for this tutorial:
-
Open a terminal shell on your local computer, if you don’t already have one open.
-
Change to the folder you are using for your Internet Computer projects, if you are using one.
-
Create a new project by running the following command:
dfx new multiple_actors -
Change to your project directory by running the following command:
cd multiple_actors
Modify the default configuration
You have already seen that creating a new project adds a default dfx.json configuration file to your project directory.
For this tutorial, you need to add sections to this file to specify the location of each program that defines an actor you want to build.
To modify the default dfx.json configuration file:
-
Open the
dfx.jsonconfiguration file in a text editor, then change the defaultmultiple_actorscanister name and source directory toassistant.For example, under the
canisterskey:"assistant": { "main": "src/assistant/main.mo", "type": "motoko" },Because you are going to add settings to this
canisterssection of the configuration file, you must also add a comma after the curly brace that encloses the location of theassistantmain source code file and the canister type. -
Remove the
multiple_actors_assetssection from the file. -
Add a new canister name, source code location, and canister type for the
rock_paper_scissorsprogram and a new canister name, source code location, and canister type for thedaemonprogram files below theassistantcanister definition.After making the changes, the
canisterssection of thedfx.jsonfile should look similar to this:{ "canisters": { "assistant": { "main": "src/assistant/main.mo", "type": "motoko" }, "rock_paper_scissors": { "main": "src/rock_paper_scissors/main.mo", "type": "motoko" }, "daemon": { "main": "src/daemon/main.mo", "type": "motoko" } }, "defaults": { "build": { "packtool": "" } }, "dfx": "0.7.2", "networks": { "local": { "bind": "127.0.0.1:8000", "type": "ephemeral" } }, "version": 1 }You can leave the other sections as-is.
-
Save your changes and close the
dfx.jsonfile to continue. -
Change the name of the default source file directory to match the name specified in the
dfx.jsonconfiguration file by running the following command:cp -r src/multiple_actors/ src/assistant/ -
Copy the
assistantsource file directory to create the main program file for therock_paper_scissorsactor by running the following command:cp -r src/assistant/ src/rock_paper_scissors/ -
Copy the
assistantsource file directory to create the main program file for thedaemonactor by running the following command:cp -r src/assistant/ src/daemon/
Modify the default programs
You now have three separate directories in the src directory, each with a template main.mo file.
For this tutorial, you will replace the content in each template main.mo file with a different actor.
To modify the default source code:
-
Open the
src/assistant/main.mofile in a text editor and delete the existing content. -
Copy and paste the following sample code into the file:
import Array "mo:base/Array"; import Nat "mo:base/Nat"; // Define the actor actor Assistant { stable var todos : [ToDo] = []; stable var nextId : Nat = 1; // Define to-do item properties type ToDo = { id : Nat; description : Text; completed : Bool; }; // Add to-do item utility func add(todos : [ToDo], description : Text, id : Nat) : [ToDo] { let todo : ToDo = { id = id; description = description; completed = false; }; Array.append(todos, [todo]) }; // Show to-do item utility func show(todos : [ToDo]) : Text { var output : Text = "\n___TO-DOs___"; for (todo : ToDo in todos.vals()) { output #= "\n(" # Nat.toText(todo.id) # ") " # todo.description; if (todo.completed) { output #= " ✔"; }; }; output }; public func addTodo (description : Text) : async () { todos := add(todos, description, nextId); nextId += 1; }; public query func showTodos () : async Text { show(todos) }; }; -
Save your changes and close the
main.mofile to continue. -
Open the
src/rock_paper_scissors/main.mofile in a text editor and delete the existing content. -
Copy and paste the following sample code into the file:
import I "mo:base/Iter"; actor RockPaperScissors { stable var alice_score : Nat = 0; stable var bob_score : Nat = 0; stable var alice_last : Choice = #scissors; stable var bob_last : Choice = #rock; type Choice = { #rock; #paper; #scissors; }; public func contest() : async Text { for (i in I.range(0, 99)) { battle_round(); }; var winner = "The contest was a draw"; if (alice_score > bob_score) winner := "Alice won" else if (alice_score < bob_score) winner := "Bob won"; return (winner); }; func battle_round() : () { let a = alice(bob_last); let b = bob(alice_last); switch (a, b) { case (#rock, #scissors) alice_score += 1; case (#rock, #paper) bob_score += 1; case (#paper, #scissors) alice_score += 1; case (#paper, #rock) bob_score += 1; case (#scissors, #paper) alice_score += 1; case (#scissors, #rock) bob_score += 1; case (#rock, #rock) alice_score += 0; case (#paper, #paper) bob_score += 0; case (#scissors, #scissors) alice_score += 0; }; alice_last := a; bob_last := b; return (); }; // Hard-coded players and choices func bob(last : Choice) : Choice { return #paper; }; func alice(last : Choice) : Choice { return #rock; }; }; -
Save your changes and close the
main.mofile to continue. -
Open the
src/daemon/main.mofile in a text editor and delete the existing content. -
Copy and paste the following sample code into the file:
actor Daemon { stable var running = false; public func launch() : async Text { running := true; debug_show "The daemon process is running"; }; public func stop(): async Text { running := false; debug_show "The daemon is stopped"; }; }; -
Save your changes and close the
main.mofile to continue.
Start the local network
Before you can build the multiple_actors project, you need to connect to the Internet Computer network either running locally in your development environment or running remotely on a subnet that you can access.
To start the network locally:
-
Open a new terminal window or tab on your local computer.
-
Navigate to the root directory for your project, if necessary.
-
Start the Internet Computer network on your local computer by running the following command:
dfx start -
Leave the terminal that displays network operations open and switch your focus to your original terminal where you created your new project.
Register, build, and deploy your application
After you connect to the Internet Computer network running locally in your development environment, you can register, build, and deploy your multi-canister application locally.
To deploy the application locally:
-
Check that you are still in the root directory for your project, if needed.
-
Register, build, and deploy the application by running the following command:
dfx deployIf you have access to the Internet Computer running remotely, you can deploy to the that network instead of deploying locally by specifying the
--networkoption and the network alias configured in thedfx.jsonfile. For example, if you are connecting to the URL specified by the network aliasicyou would run a command similar the following:dfx deploy --network icThe
dfx deploycommand output displays information about the operations it performs. For example, the command displays the network-specific canister identifiers for the three canisters defined in thedfx.jsonconfiguration file.Deploying all canisters. Creating canisters... Creating canister "assistant"... "assistant" canister created with canister id: "75hes-oqbaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q" Creating canister "daemon"... "daemon" canister created with canister id: "cxeji-wacaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q" Creating canister "rock_paper_scissors"... "rock_paper_scissors" canister created with canister id: "7kncf-oidaa-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa-q"
Verify deployment by calling functions
You now have three programs deployed as a canisters on your local replica network and can test each program by using dfx canister call commands.
To test the programs you have deployed:
-
Use the
dfx canister callcommand to call the canisterassistantusing theaddTodofunction and pass it the task you want to add by running the following command:dfx canister call assistant addTodo '("Schedule monthly demos")' -
Verify that the command returns the to-do list item using the
showTodosfunction by running the following command:dfx canister call assistant showTodosThe command returns output similar to the following:
(" ___TO-DOs___ (1) Schedule monthly demos") -
Use the
dfx canister callcommand to call the canisterrock_paper_scissorsusing thecontestfunction by running the following command:dfx canister call rock_paper_scissors contestThe command returns the result of the hard-coded contest similar to the following:
("Bob won") -
Use the
dfx canister callcommand to call the canisterdaemonusing thelaunchfunction by running the following command:dfx canister call daemon launch -
Verify the mock
launchfunction returns "The daemon process is running" message":(""The daemon process is running"")
Stop the local network
After you finish experimenting with your program, you can stop the local Internet Computer network so that it doesn’t continue running in the background.
To stop the local network:
-
In the terminal that displays network operations, press Control-C to interrupt the local network process.
-
Stop the Internet Computer network by running the following command:
dfx stop