Index: src/includes/admin/tools/repair.php
--- src/includes/admin/tools/repair.php
+++ src/includes/admin/tools/repair.php
@@ -232,7 +232,7 @@
 	$statement = __( 'Counting the number of voices in each topic&hellip; %s', 'bbpress' );
 	$result    = __( 'Failed!', 'bbpress' );
 
-	$sql_delete = "DELETE FROM `{$bbp_db->postmeta}` WHERE `meta_key` = '_bbp_voice_count'";
+	$sql_delete = "DELETE FROM {$bbp_db->postmeta} WHERE meta_key IN ('_bbp_voice_count', '_bbp_engagement')";
 	if ( is_wp_error( $bbp_db->query( $sql_delete ) ) ) {
 		return array( 1, sprintf( $statement, $result ) );
 	}
@@ -243,21 +243,29 @@
 	$pps = bbp_get_public_status_id();
 	$cps = bbp_get_closed_status_id();
 
-	$sql = "INSERT INTO `{$bbp_db->postmeta}` (`post_id`, `meta_key`, `meta_value`) (
-			SELECT `postmeta`.`meta_value`, '_bbp_voice_count', COUNT(DISTINCT `post_author`) as `meta_value`
-				FROM `{$bbp_db->posts}` AS `posts`
-				LEFT JOIN `{$bbp_db->postmeta}` AS `postmeta`
-					ON `posts`.`ID` = `postmeta`.`post_id`
-					AND `postmeta`.`meta_key` = '_bbp_topic_id'
-				WHERE `posts`.`post_type` IN ( '{$tpt}', '{$rpt}' )
-					AND `posts`.`post_status` IN ( '{$pps}', '{$cps}' )
-					AND `posts`.`post_author` != '0'
-				GROUP BY `postmeta`.`meta_value`)";
+	$voice_id_sql = $bbp_db->prepare( "INSERT INTO {$bbp_db->postmeta} (post_id, meta_key, meta_value) (
+			SELECT postmeta.meta_value, '_bbp_engagement', posts.post_author
+				FROM {$bbp_db->posts} AS posts
+				LEFT JOIN {$bbp_db->postmeta} AS postmeta
+					ON posts.ID = postmeta.post_id
+					AND postmeta.meta_key = '_bbp_topic_id'
+				WHERE posts.post_type IN (%s, %s)
+					AND posts.post_status IN (%s, %s))", $tpt, $rpt, $pps, $cps );
 
-	if ( is_wp_error( $bbp_db->query( $sql ) ) ) {
+	if ( is_wp_error( $bbp_db->query( $voice_id_sql ) ) ) {
 		return array( 2, sprintf( $statement, $result ) );
 	}
 
+	$voice_count_sql = "INSERT INTO {$bbp_db->postmeta} (post_id, meta_key, meta_value) (
+				SELECT post_id, '_bbp_voice_count', COUNT(DISTINCT meta_value)
+					FROM {$bbp_db->postmeta}
+					WHERE meta_key = '_bbp_engagement'
+					GROUP BY post_id ORDER BY post_id)";
+
+	if ( is_wp_error( $bbp_db->query( $voice_count_sql ) ) ) {
+		return array( 3, sprintf( $statement, $result ) );
+	}
+
 	return array( 0, sprintf( $statement, __( 'Complete!', 'bbpress' ) ) );
 }
 
Index: src/includes/core/update.php
--- src/includes/core/update.php
+++ src/includes/core/update.php
@@ -326,6 +326,7 @@
 				bbp_admin_upgrade_user_favorites();
 				bbp_admin_upgrade_user_topic_subscriptions();
 				bbp_admin_upgrade_user_forum_subscriptions();
+				bbp_admin_repair_topic_voice_count();
 			}
 		}
 	}
Index: src/includes/topics/functions.php
--- src/includes/topics/functions.php
+++ src/includes/topics/functions.php
@@ -910,7 +910,6 @@
 		bbp_update_topic_last_active_time   ( $topic_id, $last_active );
 		bbp_update_topic_reply_count        ( $topic_id, 0            );
 		bbp_update_topic_reply_count_hidden ( $topic_id, 0            );
-		bbp_update_topic_voice_count        ( $topic_id               );
 
 		// Walk up ancestors and do the dirty work
 		bbp_update_topic_walker( $topic_id, $last_active, $forum_id, 0, false );
@@ -2952,7 +2951,7 @@
  * @uses update_post_meta() To update the topic voice count meta
  * @uses apply_filters() Calls 'bbp_update_topic_voice_count' with the voice
  *                        count and topic id
- * @return int Voice count
+ * @return void|int Voice count.
  */
 function bbp_update_topic_voice_count( $topic_id = 0 ) {
 
@@ -2966,14 +2965,27 @@
 	}
 
 	// Query the DB to get voices in this topic
-	$bbp_db = bbp_db();
-	$query  = $bbp_db->prepare( "SELECT COUNT( DISTINCT post_author ) FROM {$bbp_db->posts} WHERE ( post_parent = %d AND post_status = %s AND post_type = %s ) OR ( ID = %d AND post_type = %s )", $topic_id, bbp_get_public_status_id(), bbp_get_reply_post_type(), $topic_id, bbp_get_topic_post_type() );
-	$voices = (int) $bbp_db->get_var( $query );
+	$bbp_db   = bbp_db();
+	$query    = "SELECT post_author FROM {$bbp_db->posts} WHERE ( post_parent = %d AND post_status = %s AND post_type = %s ) OR ( ID = %d AND post_type = %s )";
+	$prepared = $bbp_db->prepare( $query, $topic_id, bbp_get_public_status_id(), bbp_get_reply_post_type(), $topic_id, bbp_get_topic_post_type() );
+	$result   = $bbp_db->get_col( $prepared );
+	$user_ids = array_unique( array_map( 'intval', (array) $result ) );
+	$count    = count( $user_ids );
 
-	// Update the voice count for this topic id
-	update_post_meta( $topic_id, '_bbp_voice_count', $voices );
+	// Remove all voice id meta for the topic.
+	delete_post_meta( $topic_id, '_bbp_voice_id' );
 
-	return (int) apply_filters( 'bbp_update_topic_voice_count', $voices, $topic_id );
+	// Update the voice ids and count for this topic id.
+	foreach ( $user_ids as $user_id ) {
+		bbp_add_user_engagement( $user_id, $topic_id );
+	}
+
+	// Update the voice count meta.
+	update_post_meta( $topic_id, '_bbp_voice_count', $count );
+
+	$count = apply_filters( 'bbp_update_topic_voice_count', $count, $topic_id );
+
+	return bbp_number_not_negative( $count );
 }
 
 /**
Index: src/includes/users/functions.php
--- src/includes/users/functions.php
+++ src/includes/users/functions.php
@@ -261,6 +261,200 @@
 	return (bool) apply_filters( 'bbp_is_object_of_user', (bool) $retval, $object_id, $user_id, $meta_key, $meta_type );
 }
 
+/** Engagements ***************************************************************/
+
+/**
+ * Get the users who have engaged in a topic
+ *
+ * @since 2.6.0 bbPress (r6310)
+ *
+ * @param int $topic_id Optional. Topic id
+ * @uses bbp_get_users_for_object() To get user IDs who engaged
+ * @uses apply_filters() Calls 'bbp_get_topic_engagements' with the users and
+ *                        topic id
+ * @return array|bool Results if the topic has any engagements, otherwise false
+ */
+function bbp_get_topic_engagements( $topic_id = 0 ) {
+	$topic_id = bbp_get_topic_id( $topic_id );
+	$users    = bbp_get_users_for_object( $topic_id, '_bbp_engagement' );
+
+	return (array) apply_filters( 'bbp_get_topic_engagements', $users, $topic_id );
+}
+
+/**
+ * Get a user's topic engagements
+ *
+ * @since 2.6.0 bbPress (r6310)
+ *
+ * @param int $user_id Optional. User id
+ * @uses bbp_has_topics() To get the topics
+ * @uses apply_filters() Calls 'bbp_get_user_engagements' with the topic query and
+ *                        user id
+ * @return array|bool Results if user has engaged, otherwise false
+ */
+function bbp_get_user_engagements( $user_id = 0 ) {
+	$user_id     = bbp_get_user_id( $user_id );
+	$engagements = bbp_has_topics( array(
+		'meta_query' => array(
+			array(
+				'key'     => '_bbp_engagement',
+				'value'   => $user_id,
+				'compare' => 'NUMERIC'
+			)
+		)
+	) );
+
+	return apply_filters( 'bbp_get_user_engagements', $engagements, $user_id );
+}
+
+/**
+ * Get a user's engaged topic ids
+ *
+ * @since 2.6.0 bbPress (r6310)
+ *
+ * @param int $user_id Optional. User id
+ * @uses bbp_get_user_id() To get the user id
+ * @uses bbp_get_topic_post_type() To get the topic post type
+ * @uses apply_filters() Calls 'bbp_get_user_engaged_topic_ids' with
+ *                        the engaged topics and user id
+ * @return array|bool Results if user has engaged, otherwise false
+ */
+function bbp_get_user_engaged_topic_ids( $user_id = 0 ) {
+	$user_id     = bbp_get_user_id( $user_id );
+	$engagements = new WP_Query( array(
+		'fields'        => 'ids',
+		'post_type'     => bbp_get_topic_post_type(),
+		'nopaging'      => true,
+		'no_found_rows' => true,
+		'meta_query'    => array( array(
+			'key'     => '_bbp_engagement',
+			'value'   => $user_id,
+			'compare' => 'NUMERIC'
+		) )
+	) );
+
+	return (array) apply_filters( 'bbp_get_user_engaged_topic_ids', $engagements->posts, $user_id );
+}
+
+/**
+ * Check if a user is engaged in a topic or not
+ *
+ * @since 2.6.0 bbPress (r6310)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ * @uses bbp_get_user_id() To get the user id
+ * @uses bbp_get_user_engaged_topic_ids() To get the user engaged topics
+ * @uses bbp_get_topic() To get the topic
+ * @uses bbp_get_topic_id() To get the topic id
+ * @uses bbp_is_object_of_user() To check if the user has engaged
+ * @uses apply_filters() Calls 'bbp_is_user_engaged' with the bool, user id,
+ *                        topic id and engagements
+ * @return bool True if the topic is in user's engagements, otherwise false
+ */
+function bbp_is_user_engaged( $user_id = 0, $topic_id = 0 ) {
+	$retval      = false;
+	$user_id     = bbp_get_user_id( $user_id, true, true );
+	$engagements = bbp_get_user_engaged_topic_ids( $user_id );
+
+	if ( ! empty( $engagements ) ) {
+
+		// Checking a specific topic id
+		if ( ! empty( $topic_id ) ) {
+			$topic    = bbp_get_topic( $topic_id );
+			$topic_id = ! empty( $topic ) ? $topic->ID : 0;
+
+		// Using the global topic id
+		} elseif ( bbp_get_topic_id() ) {
+			$topic_id = bbp_get_topic_id();
+
+		// Use the current post id
+		} elseif ( ! bbp_get_topic_id() ) {
+			$topic_id = get_the_ID();
+		}
+
+		// Is topic_id in the user's engagements
+		if ( ! empty( $topic_id ) ) {
+			$retval = bbp_is_object_of_user( $topic_id, $user_id, '_bbp_engagement' );
+		}
+	}
+
+	return (bool) apply_filters( 'bbp_is_user_engaged', (bool) $retval, $user_id, $topic_id, $engagements );
+}
+
+/**
+ * Add a topic to user's engagements
+ *
+ * @since 2.6.0 bbPress (r6310)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ * @uses bbp_is_user_engaged() To check if the user is engaged in a topic
+ * @uses do_action() Calls 'bbp_add_user_engagement' with the user id and topic id
+ * @return bool Always true
+ */
+function bbp_add_user_engagement( $user_id = 0, $topic_id = 0 ) {
+
+	// Bail if not enough info
+	if ( empty( $user_id ) || empty( $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if no topic
+	$topic = bbp_get_topic( $topic_id );
+	if ( empty( $topic ) ) {
+		return false;
+	}
+
+	// Bail if already a engaged
+	if ( bbp_is_user_engaged( $user_id, $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if add fails
+	if ( ! bbp_add_user_to_object( $topic_id, $user_id, '_bbp_engagement' ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_add_user_engagement', $user_id, $topic_id );
+
+	return true;
+}
+
+/**
+ * Remove a topic from user's engagements
+ *
+ * @since 2.6.0 bbPress (r6310)
+ *
+ * @param int $user_id Optional. User id
+ * @param int $topic_id Optional. Topic id
+ * @uses bbp_is_user_engaged() To check if the user is engaged in a topic
+ * @uses do_action() Calls 'bbp_remove_user_engagement' with the user & topic id
+ * @return bool True if the topic was removed from user's engagements, otherwise
+ *               false
+ */
+function bbp_remove_user_engagement( $user_id, $topic_id ) {
+
+	// Bail if not enough info
+	if ( empty( $user_id ) || empty( $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if not already engaged
+	if ( ! bbp_is_user_engaged( $user_id, $topic_id ) ) {
+		return false;
+	}
+
+	// Bail if remove fails
+	if ( ! bbp_remove_user_from_object( $topic_id, $user_id, '_bbp_engagement' ) ) {
+		return false;
+	}
+
+	do_action( 'bbp_remove_user_engagement', $user_id, $topic_id );
+
+	return true;
+}
+
 /** Favorites *****************************************************************/
 
 /**
@@ -400,7 +594,7 @@
 		return false;
 	}
 
-	// Bail if to topic
+	// Bail if no topic
 	$topic = bbp_get_topic( $topic_id );
 	if ( empty( $topic ) ) {
 		return false;
